- 2024-12-14
-
加入了学习《【Follow me第二季第3期】+ EK_RA6M5作品展示视频》,观看 Follow me第二季第3期 EK_RA6M5作品视频
-
上传了资料:
Follow me第二季第3期EK_RA6M5作品代码
- 2024-12-01
-
加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门
- 2024-11-24
-
发表了主题帖:
【Follow me第二季第3期】+ EK_RA6M5作品提交任务汇总贴
本帖最后由 cqut面码 于 2024-12-15 01:09 编辑
一、总体视频演示
二、物料清单的介绍
主要物料:
EK-RA6M5 官方开发板一块,网线一根、micro USB线一个根、micro USB转接口一个、以及自己为了调试和查看串口方便自己添加了一根粉色的micro USB线
实物图片:
三、设计的思路以及软件流程图
核心设计思路,在快速开始的demo工程上,开发了一个二级菜单用于完成综合任务。根据二级菜单的输入,分别执行波形输出、数据读取、数据存储的功能。
波形的输出是支持三角波、方波、正弦波的,频率支持10HZ和100HZ,频率是通过改变定时器的中断频率来实现的。
四、任务/项目介绍
本次Follow me第二季第3期活动,需要使用EK_RA6M5开发板完成以下4个任务:
入门任务:搭建环境,下载调试示例程序,Blink,按键;
基础任务:quad-spi flash和octo-spi flash配置及读写速度测试;DAC配置生成波形及性能测试;
进阶任务:示例程序中新增命令打印信息;
扩展任务:设计一个类似信号发生器功能的例程。可在示例程序上修改。通过命令或按键,设置DAC输出波形,可通过flash存储历史波形等信息
4.1 入门任务:搭建环境,下载调试示例程序,Blink,按键
根据入门培训的视频教程和结合资料,安装e2 studio和下载示例程序。
根据培训视频中搭建,环境有一些问题。下载最新的e2 studio可以解决。
然后编译代码,一个demo代码,发现编译直接报错,根据配置,重新配置和下载toolchain即可。
如果编译还是报错,可以重新下载master的demo代码,即可。具体可以下载这个
https://github.com/renesas/ra-fsp-examples
环境搭建完毕的e2 studio,并编译示例代码成功
进行入门任务,参考官方的blink+按键示例
ra-fsp-examples-5.5.0.example.3\example_projects\ek_ra6m5\_quickstart\quickstart_ek_ra6m5_ep
官方代码是支持使用两个按键控制LED1的亮度和闪烁频率,其中S1按键控制LED1亮度,S2控制LED的闪烁频率。
目前官方demo支持亮度调节3个档次 10%、50%、90%,闪烁频率3个档次1 Hz、5Hz、10 Hz。
关键代码分析:
void gpt_blinker_callback(timer_callback_args_t *p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
if (OFF == s_blueled_flashing)
{
s_blueled_flashing = ON;
}
else
{
s_blueled_flashing = OFF;
}
}
gpt_blinker_callback gpt定时回调函数,每次进入中断回调时,改变控制LED3灯的开关状态。通过改变gpt定时器的更新中断时间,即可调整LED3灯开关状态改变的频率,从而达到控制频率的效果。
void gpt_blue_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
switch (s_blueled_flashing)
{
case ON:
{
if ((s_intense++ ) < s_duty)
{
TURN_BLUE_ON
}
else
{
TURN_BLUE_OFF
}
if (s_intense >= 100)
{
s_intense = 0;
s_duty = g_pwm_dcs[g_board_status.led_intensity];
}
break;
}
default:
{
TURN_BLUE_OFF
s_intense = 0;
s_duty = g_pwm_dcs[g_board_status.led_intensity];
}
}
}
gpt_blue_callback 回调函数,根据s_blueled_flashing的状态和s_duty 来控制LED灯的亮度。
void button_irq10_callback(external_irq_callback_args_t *p_args)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xResult = pdFAIL;
EventBits_t uxBits;
/* Void the unused args */
FSP_PARAMETER_NOT_USED(p_args);
uxBits = xEventGroupGetBitsFromISR (g_update_console_event);
if ((uxBits & (STATUS_UPDATE_INTENSE_INFO)) != (STATUS_UPDATE_INTENSE_INFO))
{
/* Cast, as compiler will assume calc is int */
g_board_status.led_intensity = (uint16_t) ((g_board_status.led_intensity + 1) % 3);
xResult = xEventGroupSetBitsFromISR(g_update_console_event, STATUS_UPDATE_INTENSE_INFO,
&xHigherPriorityTaskWoken);
/* Was the message posted successfully? */
if (pdFAIL != xResult)
{
/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context
switch should be requested. The macro used is port specific and will
be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to
the documentation page for the port being used. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
/**********************************************************************************************************************
End of function button_irq10_callback
*********************************************************************************************************************/
/* SW 2 */
/**********************************************************************************************************************
* Function Name: button_irq9_callback
* Description : SW2 interrupt handler.
* Argument : p_args
* Return Value : None
*********************************************************************************************************************/
void button_irq9_callback(external_irq_callback_args_t *p_args)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xResult = pdFAIL;
EventBits_t uxBits;
/* Void the unused args */
FSP_PARAMETER_NOT_USED(p_args);
uxBits = xEventGroupGetBitsFromISR (g_update_console_event);
if ((uxBits & (STATUS_UPDATE_FREQ_INFO)) != (STATUS_UPDATE_FREQ_INFO))
{
/* Cast, as compiler will assume calc is int */
g_board_status.led_frequency = (uint16_t) ((g_board_status.led_frequency + 1) % 3);
xResult = xEventGroupSetBitsFromISR(g_update_console_event, STATUS_UPDATE_FREQ_INFO, &xHigherPriorityTaskWoken);
/* Was the message posted successfully? */
if (pdFAIL != xResult)
{
/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context
switch should be requested. The macro used is port specific and will
be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to
the documentation page for the port being used. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
button_irq10_callback 和 button_irq9_callback 是S1和S2的按键中断回调函数,进行了按键的检测,并通过事件的方式进行通知按键的按下。
如图,编译下载程序之后,通过开发板的全速USB接口与电脑相连接,通过terminal可以查看开发板输出的配置信息,和LED1的亮度信息以及闪烁频率信息
可以看到开发板的设备相关的信息,LED blink频率以及亮度,以及每次通过S1和S2的按键可以控制频率亮度。
现在芯片的温度为24.56摄氏度,blink频率为5HZ,亮度为90%
4.2 基础任务:quad-spi flash和octo-spi flash配置及读写速度测试,DAC配置生成波形及性能测试
4.2.1 quad-spi flash和octo-spi flash配置及读写速度测试
在菜单选项中输入4,进行Quad-SPI和Octo-spi的读写速度进行测试。测试结果如下:
在SRAM中生成了一个64KB的数据,然后以两种接口形式分别写入到外部falsh中。可以看出 Quad-SPI 写入花费65207us,读取花费6374us,
而Octa-SPI写入花费30050us,读取花费1044us,读写性能显著提升。
关键代码分析:
test_fn ext_display_menu(void) 函数,截取了qspi ospi 的读写调用部分测试的关键代码。
R_GPT_InfoGet(g_memory_performance.p_ctrl, &timer_info); /*获取定时器信息*/
timer_frequency = timer_info.clock_frequency;
/*进行ospi测试*/
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; /*计算ospi 写的耗时*/
qspi_write_result = ((100000000 / timer_frequency) * qspi_write_test(block_size_actual)) / 100; /*进行qspi写测试 并计数耗时*/
/* 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; /*计算ospi 读的耗时*/
qspi_read_result = ((100000000 / timer_frequency) * qspi_read_test(block_size_actual)) / 100; /*进行qspi读测试 并计数耗时*/
/* 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); /*关闭定时器*/
ospi_performance_test (block_size_actual, &ospi_performance_write_result, &ospi_performance_read_result) 函数是进行ospi的读写测试
截取关键代码如下:
*ospi_performance_write_result = write_dopi_ospi(data_size); /*ospi 写*/
ospi_test_wait_until_wip();
*ospi_performance_read_result = read_dopi_ospi(data_size); /*ospi 读*/
ospi_test_wait_until_wip();
erase_dopi_ospi();
ospi_test_wait_until_wip();
static uint32_t write_dopi_ospi(uint32_t data_size) 关键代码,循环写数据,每次读之前开启定时器,写完之后关闭定时器
for (test_var = 0; test_var < number_of_pages; test_var++ )
{
/* Performance measured around this loop will be slightly lower due to branches and test write-in-progress
* The actual throughput should be measured with direct debugger downloads (not supported by SEGGER yet)*/
R_GPT_Start(g_memory_performance.p_ctrl); /*开启定时器的计数*/
err = R_OSPI_Write(g_ospi.p_ctrl, s_page, p_dest, OSPI_TEST_PAGE_SIZE);
if (FSP_SUCCESS != err)
{
__asm("bkpt");
}
ospi_test_wait_until_wip();
p_dest += OSPI_TEST_PAGE_SIZE;
R_GPT_Stop(g_memory_performance.p_ctrl); /*关闭定时器的计数*/
vTaskDelay(1U); /*进行任务切换,避免优先级任务饿死*/
}
static uint32_t read_dopi_ospi(uint32_t data_size),使用ospi进行读
/* Start timer */
R_GPT_Start(g_memory_performance.p_ctrl);
R_OSPI_XipEnter(g_ospi.p_ctrl);
memcpy (p_dest, p_src, (data_size * 1024) / 4);
R_OSPI_XipExit(g_ospi.p_ctrl);
/* Stop timer */
R_GPT_Stop(g_memory_performance.p_ctrl);
static uint32_t qspi_write_test(uint32_t block_size) qspi_write_test,循环写,直到写完所有数据为止
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);
static uint32_t qspi_read_test(uint32_t block_size),qspi读数据,直到读完所有数据为止
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);
4.2.2 DAC配置生成波形及性能测试
quickstart 工程里面没有DAC的配置,我们使用config图形界面直接配置DAC0 把P014作为输出引脚,注意这里需要把ADC0里面占用P014的通道给关闭了。
关键部分代码说明
在common_init函数里面添加初始化以及启动DAC的函数
fsp_err_t common_init(void)
{
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err = adc_initialize ();
if (FSP_SUCCESS != fsp_err)
{
return fsp_err;
}
fsp_err = icu_initialize ();
if (FSP_SUCCESS != fsp_err)
{
return fsp_err;
}
/* DAC0初始化函数 并开启DAC0*/
fsp_err = R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
fsp_err = R_DAC_Start(&g_dac0_ctrl);
}
偷懒用下gpt_blue_callback 定时回调函数,在里面利用DAC0 来输出一个sin波形。
#include "math.h"
#define M_PI_T 3.14159265358979323846
void gpt_blue_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
//*生成一个sin信号的波形*/
uint16_t dac_valur = 0;
static double w = 0.0f;
w +=M_PI_T/30;
if(w >= 2*M_PI_T)
{
w = 0.0f;
}
dac_valur = (uint16_t)((sin(w) + 1) /2.0f * 4095);
/*进行DAC数据写入*/
R_DAC_Write(&g_dac0_ctrl, dac_valur);
后续省略
}
使用示波器对DAC0在P014引脚输出的正弦波信号进行测量,具体如下:
软件流程图
各任务对应的主要代码片段、功能展示及图文说明(每个任务需要包含至少一张对应的实物图)
4.3 进阶任务:示例程序中新增命令打印信息
这一节的任务就是新增一个命令打印,能够通过输入数据选择运行对用命令函数。
关键代码分析:
省略了其他非关键代码,void menu_thread_create(void) 创建了菜单任务
void menu_thread_create(void)
{
/* Increment count so we will know the number of threads created in the RA Configuration editor. */
g_fsp_common_thread_count++;
/* Initialize each kernel object. */
#if 1
menu_thread = xTaskCreateStatic (
#else
//freeRTOS 创建菜单任务
BaseType_t menu_thread_create_err = xTaskCreate(
#endif
menu_thread_func,
(const char*) "Menu Thread", 24576 / 4, // In words, not bytes
(void*) &menu_thread_parameters, //pvParameters
13,
#if 1
(StackType_t*) &menu_thread_stack,
(StaticTask_t*) &menu_thread_memory
#else
& menu_thread
#endif
);
}
然后在,menu_thread_entry (pvParameters) 函数主要是去等待 usb_monitor_entry 释放的二进制信号量。如果获取到了该信号量,说明usb full 接口是已经连接上电脑了。
void menu_thread_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
SYSTEM_OK
TickType_t Semphr_wait_ticks = pdMS_TO_TICKS (WAIT_TIME);
//省略部分代码
while (1)
{
if (true == g_show_intro)
{ //等待usb_monitor_entry 释放的二进制信号量
while (pdPASS != xSemaphoreTake(g_start_menu_binary_semaphore, Semphr_wait_ticks))
{
vTaskDelay(10);
}
}
vTaskDelay(10);
main_display_menu(); //主界面显示处理逻辑
}
}
具体来分析 main_display_menu 函数,主要显示了所有注册的命令函数,以及从console中读取输入字符,然后检查参数的合法性,去调用对应的注册的命令行函数。
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 (); //读取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'));
}
所以,如果我们需要添加一个自定义的命令打印信息,只需要在s_menu_items[]中依葫芦画瓢添加一行即可,然后把命令行函数实现以及声明。
这里新增了hello_display_menu 命令函数,但是函数里面实现的内容还是和kis_display_menu保持了一致。
static st_menu_fn_tbl_t s_menu_items[] =
{
{"hello Information" , hello_display_menu}, /*自定义命令函数*/
{"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},
{"Next Steps", ns_display_menu },
{"", NULL }
};
下载运行代码
打印出所有的命令函数的菜单,可以看到我们添加的选项1 hello information
输入1,执行自定义命令的结果
4.4 扩展任务:设计一个类似信号发生器功能的例程。可在示例程序上修改,通过命令或按键,设置DAC输出波形,可通过flash存储历史波形等信息
直接在快速开始工程上进行修改代码,来完成本次的拓展任务。分为以下几个步骤来进行完善。
4.4.1 设计一个简易信号发生器的二级命令菜单
在示例代码的原有上,添加一个简易信号发生器的二级菜单。
输入数字8进入信号发生器功能。
选择对应的二级菜单数据,就会执行相应的功能,一共有8个功能。直接输入空格会返回上一级菜单,直到返回主菜单为止。
比如选择了1功能,然后输入空格,就会返回上一级的子菜单。
下面介绍信号发生器的菜单任务的具体代码实现:
主菜单里面添加了简易信号发生器二级菜单
/* Table of menu functions */
static st_menu_fn_tbl_t s_menu_items[] =
{
{"hello Information" , hello_display_menu}, /*自定义命令函数*/
{"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},
{"Next Steps", ns_display_menu },
{"Simple Signal Generator", simple_signal_generator}, /*添加简易信号发生器菜单*/
{"", NULL }
};
然后,进入子菜单的逻辑处理。模仿了主菜单的方式进行编写,先是清屏然后输出子菜单的所有命令,然后,根据输入的数据执行对应子菜单命令,检查到如果输入是空格。就退出子菜单,回到主菜单。
子菜单的命令集为:
/*简易信号发生器二级菜单*/
static st_menu_fn_tbl_t scope_menu_items[] =
{
{ "10HZ sin wave signal output", sin_wave_10hz_output },
{ "100HZ sin wave signal output", sin_wave_100hz_output },
{ "10HZ triangular wave signal output", triangular_wave_10hz_output },
{ "100HZ triangular wave signal output", triangular_wave_100hz_output },
{ "10HZ square wave signal output", square_wave_10hz_output },
{ "100HZ square wave signal output", square_wave_100hz_output },
{ "FLASH stores the waveform data", flash_store_wave_data },
{ "Read the FALSH waveform data and output", read_flash_store_wave_data },
{ "", NULL } };
接着是子菜单的处理逻辑代码:
/**
* 简易信号发生器的实现
* @return
*/
test_fn simple_signal_generator(void)
{
int8_t c = -1;
int8_t menu_limit = 0;
int8_t exit_submenu = 0;
/*DAC和定时器初始化 以及产生原始波形*/
simple_oscilloscope_init ();
/*检查是否需要退出波形输出子菜单*/
while (0 == exit_submenu)
{
//进行清空数据
c = -1;
menu_limit = 0;
exit_submenu = 0;
vTaskDelay (10);
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, SCOPE_MOUDLE_NAME, g_selected_menu);
/* 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, SCOPE_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 != scope_menu_items[test_active].p_func; test_active++)
{
sprintf (s_print_buffer, "\r\n %d. %s", (test_active + 1), scope_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 (); //读取console 输入数据
if (0 != c)
{
/*退出子菜单 回到主菜单*/
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
exit_submenu = 1;
/*退出前把 定时器和DAC全部停止*/
simple_oscilloscope_deinit ();
break;
}
/* Cast, as compiler will assume calc is int */
c = (int8_t) (c - '0');
g_selected_sub_menu = c;
if ((c > 0) && (c <= menu_limit)) //检查输入的合理性
{
scope_menu_items[c - 1].p_func (); //根据输入数字索引调用对应函数
exit_submenu = 0;
break;
}
}
}
}
return 0;
}
4.4.2 设置DAC根据配置参数输出波形数据
这里的思路是,单独配置一个GPT定时器,在每次中断回调函数中,根据波形数组的数据,通过DAC来输出,从而实现波形发生器的功能。输出波形的频率是通过定时器的
更新中断频率来决定的。
首先根据图形界面配置一个定时器出来使用。根据下面的配置,配置出来一个100us中断一次的定时器,然后回调函数为gpt_dac_output_callback
初始化时,初始化DAC 以及定时器,并生成正弦波、三角波、方波的数据到数组中
/**
* 简易示波器硬件配置初始化
*/
void simple_signal_generator_init(void)
{
fsp_err_t fsp_err = FSP_SUCCESS;
if(0 != oscilloscope_init_flag)
{
return;
}
/*初始化硬件*/
/* DAC0初始化函数 并开启DAC0*/
fsp_err = R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
fsp_err = R_DAC_Start(&g_dac0_ctrl);
GenerateWavedata(); /*生成波形数据*/
/*配置定时中断输出的DAC值*/
R_GPT_Open (&g_dac_output_timer_ctrl,&g_dac_output_timer_cfg);
R_GPT_Start(g_dac_output_timer.p_ctrl);
/*设置默认输出波形*/
SetWaveform(WAVE_SINE, FREQ_1HZ);
oscilloscope_init_flag = 1;
return;
}
波形数据的产生就是生成了3个 波形数据到数组中,便于DAC直接取数组的数据输出
static void GenerateSineWave(uint16_t *wave)
{
for (int i = 0; i < SAMPLE_COUNT; i++)
{
float angle = 2 * M_PI_T * i / SAMPLE_COUNT;
wave[i] = (uint16_t)((DAC_MAX - DAC_MIN) / 2 * (sin(angle) + 1));
}
}
static void GenerateSquareWave(uint16_t *wave)
{
for (int i = 0; i < SAMPLE_COUNT; i++)
{
wave[i] = (i < SAMPLE_COUNT / 2) ? DAC_MAX : DAC_MIN;
}
}
static void GenerateTriangleWave(uint16_t *wave)
{
for (int i = 0; i < SAMPLE_COUNT; i++)
{
if (i < SAMPLE_COUNT / 2)
{
wave[i] = (uint16_t)((DAC_MAX - DAC_MIN) * 2 * i / SAMPLE_COUNT);
} else
{
wave[i] = (uint16_t)(DAC_MAX - (DAC_MAX - DAC_MIN) * 2 * (i - SAMPLE_COUNT / 2) / SAMPLE_COUNT);
}
}
}
static void GenerateWavedata(void)
{
GenerateSineWave(sineWave);
GenerateTriangleWave(triangleWave);
GenerateSquareWave(squareWave);
}
当需要更改波形的类型和频率时,只需要重新调用以下函数,重新配置定时器的周期和DAC输出数组类型即可
/*设置波形参数*/
void SetWaveform(WaveType waveType, Frequency frequency)
{
/*设置时进行保护*/
__disable_irq();
currentWaveType = waveType;
currentFrequency = frequency;
step = 0; // Reset time counter
__enable_irq();
uint32_t period_counts = 0;
switch (frequency)
{
case FREQ_100HZ:
period_counts = 9600;
break;
case FREQ_10HZ:
period_counts = 96000;
break;
case FREQ_1HZ:
period_counts = 960000;
break;
default: /*100HZ*/
period_counts = 9600;
break;
}
R_GPT_Stop(&g_dac_output_timer_ctrl); // 停止当前定时器
R_GPT_PeriodSet(&g_dac_output_timer_ctrl, period_counts); /*设置新的定时器周期 调整中断频率*/
R_GPT_Start(&g_dac_output_timer_ctrl);
}
真正的DAC输出数据,是在定时器的中断回调函数中执行的:
/*gpt 定时回调函数 根据参数来改变输出的DAC波形和频率*/
void gpt_dac_output_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
uint16_t dac_value;
uint16_t *waveform;
switch (currentWaveType)
{
case WAVE_SINE:
waveform = sineWave;
break;
case WAVE_SQUARE:
waveform = squareWave;
break;
case WAVE_TRIANGLE:
waveform = triangleWave;
break;
default:
waveform = sineWave;
break;
}
dac_value = waveform[step % SAMPLE_COUNT];
R_DAC_Write(&g_dac0_ctrl, dac_value); /*DAC 输出数据值*/
step++;
}
将示波器的探头与开发板的P014 相连接,具体如下图。
打开示波器的进行测量,可以看到根据选择。分别输出了100H、10HZ的正弦波、三角波、方波。
4.4.3 FLAHS存储历史波形数据
这里参考了官方demo里面的qspi的代码。在二级菜单中输入8就是选择波形数据存储,输入9就是把存储的波形数据读取出来,并通过串口打印出来。
重点实现的就是这两个函数
flash_store_wave_data 把生成波形数据通过SPI接口写入外部flash。
test_fn flash_store_wave_data(void)
{
int8_t c = -1;
/*进行清屏操作*/
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_FLASH_STORE, g_selected_sub_menu);
/* 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, MENU_RETURN_INFO);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console ((void*) s_print_buffer);
data_write_spi_flash ();
while (CONNECTION_ABORT_CRTL != c)
{
c = input_from_console ();
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
break;
}
}
return (0);
}
下面是详细的通过qspi写入三角波波形数据的过程,初始化qspi,然后把三角波形数据 从uint16 数据转化为 uint8 数据写入存储。最后再读取出来,并进行比较写入的数据和读取数据是否相等来判断是否数据写入成功,最后再deinit qspi接口。
void data_write_spi_flash(void)
{
fsp_err_t err = FSP_SUCCESS;
uint8_t data_write[PAGE_WRITE_SIZE] =
{ RESET_VALUE, };
uint8_t verify_written_data[PAGE_WRITE_SIZE] =
{ RESET_VALUE, };
uint8_t data_sreg[SREG_SIZE] = STATUS_REG_PAYLOAD;
uint8_t *p_mem_addr = (uint8_t*) QSPI_DEVICE_START_ADDRESS;
if (SPI_FLASH_PROTOCOL_QPI == g_qspi_cfg.spi_protocol)
{
/*
* this needs to be done since QPI is set by user in configuration
* and it sets QPI only in MCU but not in flash device
* so as a system (MCU + QSPI flash device) QPI mode does not get set by
* simply calling only R_QSPI_Open in QPI mode.
* Rather QPI mode enabling has to be done in Flash device as well
* So opening the driver in extended SPI mode only
* and QPI mode is enabled when qpi_mode_set sub-function is called
*/
spi_flash_cfg_t l_qspi_cfg;
memcpy ((spi_flash_cfg_t*) &l_qspi_cfg, (spi_flash_cfg_t*) &g_qspi_cfg, sizeof(spi_flash_cfg_t));
l_qspi_cfg.spi_protocol = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* open QSPI with local configuration */
err = R_QSPI_Open (&g_qspi_ctrl, &l_qspi_cfg);
if (FSP_SUCCESS != err)
{
}
}
else
{
/* open QSPI in extended SPI mode */
err = R_QSPI_Open (&g_qspi_ctrl, &g_qspi_cfg);
if (FSP_SUCCESS != err)
{
}
}
/* write enable for further operations */
err = R_QSPI_DirectWrite (&g_qspi_ctrl, &(g_qspi_cfg.write_enable_command), ONE_BYTE, false);
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
else
{
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
}
/*
* write QSPI flash status register
* This is required to make sure the device is ready for general
* read write operation,
* This performs settings such as physical reset,WP hardware pin disable,
* block protection lock bits clearing.
* for more details please refer Mx25L data sheet.
*/
err = R_QSPI_DirectWrite (&g_qspi_ctrl, data_sreg, SREG_SIZE, false);
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
else
{
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
}
/*
* Verifying data written to QSPI flash status register
* Step 1: - send command byte - 0x05
* through R_QSPI_DirectWrite with last argument set as true
* Step 2 - read data through R_QSPI_DirectRead
*/
uint8_t sreg_data = RESET_VALUE;
err = R_QSPI_DirectWrite (&g_qspi_ctrl, &(g_qspi_cfg.status_command), ONE_BYTE, true);
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
/*
* we should not call function get_flash_status here
* because the CS line should not get interrupted between write read
*
* Also MCU <SFMCD register> is set as 0 when status register is read
* to resume in ROM access mode hence API direct read returns error as part
* of parameter check itself
*/
err = R_QSPI_DirectRead (&g_qspi_ctrl, &sreg_data, ONE_BYTE);
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
else
{
/* check for status check operation here */
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
}
/* verify read status register data */
if (SET_SREG_VALUE != sreg_data)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
if (SPI_FLASH_PROTOCOL_QPI == g_qspi_cfg.spi_protocol)
{
/* set QPI mode in flash and MCU device */
err = qpi_mode_set ();
if (FSP_SUCCESS != err)
{
/* close QSPI module which is currently in extended SPI mode only */
deinit_qspi (SPI_FLASH_PROTOCOL_EXTENDED_SPI);
}
}
/* Erase Flash for one sector */
err = R_QSPI_Erase (&g_qspi_ctrl, p_mem_addr, SECTOR_SIZE);
if (FSP_SUCCESS != err)
{
deinit_qspi (g_qspi_cfg.spi_protocol);
}
else
{
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
deinit_qspi (g_qspi_cfg.spi_protocol);
}
/* validating erase */
for (uint16_t mem_index = RESET_VALUE; mem_index < SECTOR_SIZE; mem_index++)
{
if (DEFAULT_MEM_VAL != p_mem_addr[mem_index])
{
deinit_qspi (g_qspi_cfg.spi_protocol);
}
}
}
/* Fill the data write buffer further to be written in QSPI flash device */
/*填充写入的三角波形数据 把uint16 数据转化为 uint8 数据存储*/
for (size_t i = 0; i < SAMPLE_COUNT; ++i)
{
data_write[2 * i] = (uint8_t) (triangleWave[i] & 0x00FF); // 低8位
data_write[2 * i + 1] = (uint8_t) ((triangleWave[i] >> 8) & 0x00FF); // 高8位
}
/* Write data to QSPI Flash */
err = R_QSPI_Write (&g_qspi_ctrl, data_write, (uint8_t*) QSPI_FLASH_ADDRESS(PAGE_FIRST), PAGE_WRITE_SIZE);
if (FSP_SUCCESS != err)
{
deinit_qspi (g_qspi_cfg.spi_protocol);
}
else
{
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
deinit_qspi (g_qspi_cfg.spi_protocol);
}
}
/* Read data from QSPI memory region */
memcpy (verify_written_data, (uint8_t*) QSPI_FLASH_ADDRESS(PAGE_FIRST), PAGE_WRITE_SIZE);
/* Verify the written data */
if (FSP_SUCCESS == (fsp_err_t) (memcmp (verify_written_data, data_write, PAGE_WRITE_SIZE)))
{
sprintf (s_print_buffer, "flash store triangle Wave data success\r\n");
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console ((void*) s_print_buffer);
}
else
{
}
/* close QSPI module */
deinit_qspi (g_qspi_cfg.spi_protocol);
}
数据写入的具体实现效果如下:写入成功后会有打印的字符串进行提示
接着是 read_flash_store_wave_data 函数,把写入flash的数据读取出来,并串口输出打印,
/**
* read_flash_store_wave_data (1HZ triangular wave )
* @return
*/
test_fn read_flash_store_wave_data(void)
{
int8_t c = -1;
uint8_t read_flash_data[PAGE_WRITE_SIZE] =
{ RESET_VALUE, };
uint16_t read_trans_flash_data[SAMPLE_COUNT] ={ RESET_VALUE, };
/*进行清屏操作*/
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_FLASH_READ, g_selected_sub_menu);
/* 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, MENU_RETURN_INFO);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console ((void*) s_print_buffer);
fsp_err_t err = FSP_SUCCESS;
if (SPI_FLASH_PROTOCOL_QPI == g_qspi_cfg.spi_protocol)
{
/*
* this needs to be done since QPI is set by user in configuration
* and it sets QPI only in MCU but not in flash device
* so as a system (MCU + QSPI flash device) QPI mode does not get set by
* simply calling only R_QSPI_Open in QPI mode.
* Rather QPI mode enabling has to be done in Flash device as well
* So opening the driver in extended SPI mode only
* and QPI mode is enabled when qpi_mode_set sub-function is called
*/
spi_flash_cfg_t l_qspi_cfg;
memcpy ((spi_flash_cfg_t*) &l_qspi_cfg, (spi_flash_cfg_t*) &g_qspi_cfg, sizeof(spi_flash_cfg_t));
l_qspi_cfg.spi_protocol = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* open QSPI with local configuration */
err = R_QSPI_Open (&g_qspi_ctrl, &l_qspi_cfg);
if (FSP_SUCCESS != err)
{
}
}
else
{
/* open QSPI in extended SPI mode */
err = R_QSPI_Open (&g_qspi_ctrl, &g_qspi_cfg);
if (FSP_SUCCESS != err)
{
}
}
/* Read data from QSPI memory region */
memcpy (read_flash_data, (uint8_t*) QSPI_FLASH_ADDRESS(PAGE_FIRST), PAGE_WRITE_SIZE);
/*进行uint8数据 转化为uint16数据 并打印输出*/
for (size_t i = 0; i < SAMPLE_COUNT ; ++i)
{
read_trans_flash_data[i] = (uint16_t)read_flash_data[2 * i] | ((uint16_t)read_flash_data[2 * i + 1] << 8);
sprintf (s_print_buffer,"read_trans_flash_data[%d]:%d\r\n",i,read_trans_flash_data[i]);
print_to_console ((void*) s_print_buffer);
}
/*和原始产生的波形数据进行对比,数据是否一致*/
/* Verify the written data */
if (FSP_SUCCESS == (fsp_err_t) (memcmp (read_trans_flash_data, triangleWave, SAMPLE_COUNT)))
{
sprintf (s_print_buffer,"read_trans_flash_data == triangleWave\r\n");
}
else
{
sprintf (s_print_buffer,"read_trans_flash_data != triangleWave\r\n");
}
print_to_console ((void*) s_print_buffer);
while (CONNECTION_ABORT_CRTL != c)
{
c = input_from_console ();
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
break;
}
}
return (0);
}
波形数据读取的具体实现效果如下:最后可以看到验证了读取出来的波形是否是生成写入的三角波形数据
五、对本活动的心得体会
非常的感谢DigiKey联合EEWorld发起的Follow me大型开发板体验活动,让电子爱好者有学习和体验到不同开发板以及新技术的魅力。邀请的大咖和论坛里面的技术氛围
非常的浓厚,自己学习到了很多知识。期望明年可以继续举办Follow me活动,也期望可以体验到更多的mcu板子和linux板子,hhhh 小小期待一下,可以推出linxu开发板的活动。
六、附件代码
https://download.eeworld.com.cn/detail/cqut%E9%9D%A2%E7%A0%81/635243
- 2024-10-19
-
加入了学习《【Follow me 第二季第2期任务】所有任务汇总进行设计任务效果展示》,观看 【Follow me 第二季第2期任务】4个任务汇总视频
-
加入了学习《【Follow me第二季第2期】任务汇总贴》,观看 【Follow me第二季第2期】任务汇总贴
- 2024-09-30
-
发表了主题帖:
【Follow me 第二季第2期任务】任务汇总贴
本帖最后由 cqut面码 于 2024-10-19 21:51 编辑
1、所有任务的视频展示
视频中一种展示了4个任务,各个任务的时间节点如下:
视频中时间节点说明
对应具体完成的任务
00:00 —— 01:45
入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!
01:45 —— 06:32
基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线。
06:32 —— 09:51
进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)。
09:51 —— 12:00
扩展任务二:通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据。
2、使用物料的展示
ARDUIINO UNO R4 WIFI开发板
SHT40传感器
面板板
电阻
杜邦线若干
具体如下所示:
3、任务实现的详细
入门任务 搭建环境、Blink 、串口打印输出
入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!:
https://bbs.eeworld.com.cn/thread-1293710-1-1.html
一、环境的搭建
首先,根据自己电脑的操作系统从官网上下载 Arduino IDE https://www.arduino.cc,进行安装。然后打开Arduino IDE,把Arduino UNO R4 开发板通过type-c 线连接到电脑上。此时,IDE上如下图1中需要选择串口连接上的开发板版本型号,右下角会自动弹出安装对应的软件包,如下图2,点击等待安装完成即可。
图1.选择连接Arduino UNO R4开发板
图2.等待软件包的安装
二、软件流程图
三、运行Blink
点击文件,新建项目,然后进行保存。第一个我们要实现的是点灯,没错 哈哈哈哈,一个合格的嵌入式工程师就是得会各种点灯。
打开 Arduino UNO R4 的原理图,如下图3,可以看到LED的引脚是P102。
图3. LED引脚控制图
由原理图可知,直接控制P102 高低就可以实现DL4 灯进行翻转。根据Arduino UNO R4的引脚图,图4,可以知道P102是开发板子的D13。
图4. 开发板引脚图
所以代码中直接控制D13就可以了。具体代码如下,主要就是500ms去翻转一次电平,每次翻转前先读取电平,然后再取反电平进行翻转
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
四、串口输出
官方文档告诉了D0 和D1 被用于USB的串口输入和输出。所以不需要我们再进行串口0的IO配置,直接拿来使用就可以
首先初始化设置波特率为115200,然后直接在loop循环函数中每500ms输出一次串口数据,具体代码如下:
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
uart_init(115200); /* 串口初始化 */
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
Serial.println("Hello EEWorld !");
}
然后打开串口监视器,设置好波特率为115200,即可看到串口监视器每500ms输出一帧串口数据了。
基础任务 点阵LED+DAC正弦波+OPAMP+波形显示
基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线:
https://bbs.eeworld.com.cn/thread-1294700-1-1.html
1、前言
前面学习了环境的搭建、LED的点亮翻转、以及串口的输出。接着就来学习基础任务里面的一些内容, 驱动12x8点阵LED和用DAC生成正弦波。
2、 驱动12x8点阵LED
先放官方的链接https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/,官方给出了详细的这个LED矩阵点亮的实现方式,我们可以通过官方的文档来进行学习,
并完成这个矩阵LED的实验,嵌入式就是得多看看官方资料,不要一个人闭门造车。快速实现了,再去了解原理,深入消化,才不会打击学习的积极性。
官方已经给出一个驱动点了这个矩阵LED库函数,我们只需要包含使用就好了。如下是输出一个爱心的框图,详细的各个功能都在代码中进行了注释说明
核心思想就是通过把自己要显示的LED矩阵相关的数据,直接写入就好,实际就是通过IO通过LED矩阵的亮灭。
画出一个心形的代码的软件流程图:
画出一个心形的代码如下:
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
ArduinoLEDMatrix led_matrix; //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际效果如下:
3、 用DAC生成正弦波
Arduino UNO R4 WiFi 内置DAC(数模转换器),用于将数字信号转换为模拟信号。这里我们直接用DAC输出一个正弦波,然后用示波器去观察器输出的波形
先看原理图DAC的输出口是哪个,从原理图上可以看到,开发板上引出的DAC引脚为A0,等会我们直接用示波器测量A0即可。
然后,参考官网的代码 https://docs.arduino.cc/tutorials/uno-r4-wifi/dac/,完成DAC正弦波的输出。
利用DAC生成正弦波输出的流程图如下:
代码我们在前面的基础上继续完善,具体如下:
这里只是生成可一个50HZ的sin波形并没有在循环中去改变频率或者波形等其他进操作
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际示波器测试的效果如下,可以看出最大值为4.6v,最小值为-13mv,频率为49.9HZ左右,波形确实比较难看,锯齿状态还是很严重的,按理说这个板子的12位的DAC性能应不止这样,
应该是内部封装的效果导致一个周期数据输出太少的原因,看来还是得上IDF编程才行。
另外还有一个问题就是,为啥最大值为4.6V,而不是5V,我怀疑是设计缺陷,DAC的参考电压不准,不是5V。时间原因就不去验证了
4、 使用OPAMP对上面的DAC信号放大
OPAMP 实际用处还是很大的,在性能要求不高的情况下,直接使用板载的OPAMP进行放大信号,可以节约成本,避免外部在接一个运放。
当然OPAMP还有一个作用就是做电压跟随器,电压跟随器具有高输入阻抗和低输出阻抗——这是它们缓冲作用的本质,它们增强信号,从而允许高阻抗源驱动低阻抗负载。
这里我们就只展示放大的作用。具官方文档可以参考学习https://docs.arduino.cc/tutorials/uno-r4-wifi/opamp
从前面的实验已经看出了,直接输出的波形已经幅度值已经达到了4.7V,这已经是DAC输出的最大值了。如果我们再在这个基础上去放大这个信号,无疑就会超过4.7V。
就会出现两种情况 1、会出现削波 2、可能损坏电路板。为了后续放大实验方便,我们限制一下幅度值,输出到最大值的1/4即可,便于后续放大波形:
代码很简单,实际就是在setup中多了一个对波形幅值的限制, wave.amplitude(0.25); //设置幅度值为4.7 的1/4
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际示波器实测效果如果下,还是挺符合4.7V的 1/4 波形的。
好了,有了基础波形,我们就来搭建运放把。额,为了搭建运放的电路,我去找了好久的电阻,建议下次不搞这种,薄膜电阻真的很难找呀,还得找块面包板。
运算放大器放大电路示意图如下所示:
实际用于两个10K的电阻和洞洞板搭建的电路图如下所示:
下面是搭建了2倍放大后的示波器效果图:黄色为放大前的波形图,蓝色为经过放大后的波形图。
附上本次实验的代码:
相对于前面而言,只是添加了一句代码。打开了OPAMP
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
#include <OPAMP.h>
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
5、上位机波形展示
我们使用A4 和 A5 引脚分别采集DAC的输出,和经过OPAMP放大之后的信号输出。然后通过串口发送到上位机进行波形的展示。
使用了 analogReadResolution(14); //change to 14-bit resolution 配置ADC的分辨率最高为14bit
使用了 float Rvalue1 = analogRead(A4) * 4.7/16383; //读取A4的引脚电压
整体的软件流程图如下:
实际的代码如下:
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
#include <OPAMP.h>
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(2000000);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
//配置ADC的分辨率最高为14bit
analogReadResolution(14); //change to 14-bit resolution
}
void loop() {
float Rvalue1 = analogRead(A4) * 4.7/16383; //读取A4的引脚电压
float Rvalue2 = analogRead(A5) * 4.7/16383; //读取A5的引脚电压
Serial.print(Rvalue1);
Serial.print(",");
Serial.println(Rvalue2);
delay(2);
}
最后我们把我们的波形在IDE上自带的串口绘图仪展示出来,可以看到基本上波形2是波形1的2倍数据关系。
这个实验中,为了方便展示波形,我们把串口的波特率调整到了最高
基础任务 MQTT协议接入到开源的智能家居平台HA
进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant):
https://bbs.eeworld.com.cn/thread-1295058-1-1.html
一、Home Assistant 的安装
为了方便操作,这里直接在电脑上使用Docker进行安装。
官网上下载 https://www.docker.com/products/docker-desktop/ Docker安装包。
安装好了Docker之后,就需要在Docker中拉取Home Assistant 镜像了。可以参考大佬博主的一篇文章,有详细的介绍命令行的使用,我这里只是展示效果了。
https://bbs.eeworld.com.cn/thread-1293807-1-1.html
(1) 第一步先用cmd 搜索镜像
docker search home-assistant
(2)第二步进行拉取最新的镜像
docker pull homeassistant/home-assistant
漫长的等Docker中拉取Home Assistant镜像中。
(3)第三步进行进行创建容器
其中HA_container为可自定义的容器名称,D:/ProgramData/HA为可自定义配置保存路径(注意是右斜杠)
docker run -d --name="HA_container" -v D:/ProgramData/HA:/config -p 8123:8123 homeassistant/home-assistant
(4)最后一步在浏览器中输入 http://localhost:8123/ 就可看到成功了。
接着注册用户,进入主页就好了,具体如下:
二、EMQX 的安装
(1)第一步在Docker中拉取EMQX镜像
CMD中输入命令行,选择第一个进行安装
docker search emqx
docker pull emqx
然后又是漫长的拉取等待
(2)第二步创建EMQX容器
cmd输入以下命令,其中HA为可自定义的容器名称,D:/ProgramData/HA为可自定义配置保存路径(注意是右斜杠)
3)第三步测试EMQX是否OK
浏览器中输入网址 http://localhost:18083/ 默认账号:admin 默认密码:public,成功的界面如下
(4)设置EMQ
一路默认创建我们的内置数据库
再创建一个用户
然后打开docker 就可以看到我们安装的 home assistant 和 EMQ
(5)然后进行EMQ和HA之间的连接
找到EMQ的集群概览。找到节点的信息,如下
打开HA,设置添加MQTT
代理名字填前面在EMQ中获取到的地址,然后创建就OK了。
回到EMQ,可以发现HA已经通过MQTT接入可EMQ,说明HA与EMQ已经成功链接了。
三、实现WIFI联网功能
一步一来做,想要MQTT上传数据,就需要先学会怎么把板子通过WIFI连接上网络。
直接如下面所示,使用开发板的官方示例来进行连接。我们只要修改相应的参数即可了
/*
This example connects to an unencrypted WiFi network.
Then it prints the MAC address of the WiFi module,
the IP address obtained, and other network details.
created 13 July 2010
by dlf (Metodo2 srl)
modified 31 May 2012
by Tom Igoe
Find the full UNO R4 WiFi Network documentation here:
https://docs.arduino.cc/tutorials/uno-r4-wifi/wifi-examples#connect-with-wpa
*/
#include <WiFiS3.h>
#include "arduino_secrets.h"
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS; // the WiFi radio's status
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
// don't continue
while (true);
}
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println("Please upgrade the firmware");
}
// attempt to connect to WiFi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
// you're connected now, so print out the data:
Serial.print("You're connected to the network");
printCurrentNet();
printWifiData();
}
void loop() {
// check the network connection once every 10 seconds:
delay(10000);
printCurrentNet();
}
void printWifiData() {
// print your board's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print your MAC address:
byte mac[6];
WiFi.macAddress(mac);
Serial.print("MAC address: ");
printMacAddress(mac);
}
void printCurrentNet() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print the MAC address of the router you're attached to:
byte bssid[6];
WiFi.BSSID(bssid);
Serial.print("BSSID: ");
printMacAddress(bssid);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.println(rssi);
// print the encryption type:
byte encryption = WiFi.encryptionType();
Serial.print("Encryption Type:");
Serial.println(encryption, HEX);
Serial.println();
}
void printMacAddress(byte mac[]) {
for (int i = 0; i < 6; i++) {
if (i > 0) {
Serial.print(":");
}
if (mac[i] < 16) {
Serial.print("0");
}
Serial.print(mac[i], HEX);
}
Serial.println();
}
唯一的修改点就是把 SECRET_SSID 和 SECRET_PASS分别替换为自己家里的WIFI名称和密码。注意,不要使用5G频段的WIFI,使用2.4G的WIFI,5G频段WIFI连接不上,应该是硬件不支持的原因。
修改如下:
#define SECRET_SSID "ChinaNet-T6zw"
#define SECRET_PASS "chu7p4s9"
编译下载,打开串口监视器,可以看到已经获取到了MAC地址和WIFI的强度,说明连接成功了
四、MQTT上传数据的准备工作
接着就需要开始准备MQTT相关的东西了
(1) MQTT BOX 的下载准备
MQTTBUX是一个客户端,方便我们调试的时候看MQTT发布的数据,下载链接如下:
https://apps.microsoft.com/detail/9nblggh55jzg?hl=zh-mo&gl=MO
安装之后,就进行一些基本的配置就可以使用了。
MQTT服务器的账户和密码就是我们前面设置的,服务器地址就是我们电脑的本机IP地址。EMQ(MQTT服务器)是通过docker安装在自己本地电脑上的,所有地址是本机电脑地址,直接使用ipconfig 命令可查。如果是把EMQ装在了树莓派上,需要填写树莓派的IP会地址。
设置好之后直接订阅一个主题就好了
这里我们订阅了这个主题 UNO/arduino/myAnalogInput/stat_t,等会开发板向这个主题发送数据,这个客户端就可以收到。
(2) Arduino IDE 安装 ArduinoMqttClient 库
具体如下图
五、代码实战实现数据上MQTT
实现了通过ADC A3接口来读取A0接口 DAC输出sin信号的电压值,然后把这个值通过MQTT协议发布出去,
实际的物理连接如下:
软件流程图如下:
具体的实现代码如下
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include <WiFiClient.h>
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "ChinaNet-T6zw"; //WIFI名字
char pass[] = "chu7p4s9"; //WIFI密码
int status = WL_IDLE_STATUS;
//实例化
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
analogWave wave(DAC); //实例化一个对象
const char broker[] = "192.168.1.11"; //MQTT服务器地址
int port = 1883; //MQTT服务器端口
const char topic[] = "UNO/arduino/myAnalogInput/stat_t"; //发布的主题
const char mqttuser[] = "lang.you"; //MQTT服务器的用户名
const char mqttpassword[] = "123456789"; //MQTT服务器密码
//set interval for sending messages (milliseconds)
const long interval = 8000;
unsigned long previousMillis = 0;
int count = 0;
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(2000000);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
//A0 DAC 输出一个波形
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
// 设置ADC的分辨率
analogReadResolution(14); //change to 14-bit resolution
// attempt to connect to Wifi network:
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
// failed, retry
Serial.print(".");
delay(5000);
}
Serial.println("You're connected to the network");
Serial.println();
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword(mqttuser, mqttpassword);
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
delay(1000);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
}
void loop() {
// call poll() regularly to allow the library to send MQTT keep alive which
// avoids being disconnected by the broker
mqttClient.poll();
//通过ADC采集 A0上输出的sin波形
float Rvalue = analogRead(A3) * 4.7/16383; //读取A0引脚的值 A3和 A0连接 14bit ADC采样
mqttClient.beginMessage(topic);
mqttClient.print(Rvalue); //把读取的值进行上传到MQTT服务器
mqttClient.endMessage();
delay(10);
}
查看MQTT BOX,并订阅UNO/arduino/myAnalogInput/stat_t主题下面的数据,可以看到数据一直在推送刷新
打开HA查看上传的电压数据是一直在刷新的
这里为什么我的HA会有这个值的显示呢,其实可以参考直播大佬的配置,详情可见下面链接。实际配置了configuration.yaml文件,增加以下代码保存。
https://bbs.eeworld.com.cn/thread-1293807-1-1.html
mqtt:
- button:
- unique_id: arduino uno button A
name: "Arduino Click me A"
command_topic: "UNO/arduino/myButtonA/cmd_t"
payload_press: "restart"
- unique_id: arduino uno button B
name: "Arduino Click me B"
command_topic: "UNO/arduino/myButtonB/cmd_t"
payload_press: "restart"
- sensor:
- unique_id: arduino uno Time
name: "arduino Time"
state_topic: "UNO/arduino/myUptime/stat_t"
suggested_display_precision: 1
unit_of_measurement: "s"
value_template: "{{ value }}"
- unique_id: arduino uno Voltage
name: "arduino Volt"
state_topic: "UNO/arduino/myAnalogInput/stat_t"
suggested_display_precision: 3
unit_of_measurement: "V"
value_template: "{{ value }}"
再来看看EMQ的情况
实际显示了3设备连接,一个就是我们板子,一个HA,一个是MQTT BOX,三个都作为了MQTT的客户端连接到了MQTT服务器
拓展任务 SHT40温湿度数据上次HA,并在HA面板显示
扩展任务二:通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据:
https://bbs.eeworld.com.cn/thread-1295288-1-1.html
1、使用到的硬件介绍
这次实验的主要实现目标是通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据。想了解一下SHT40这个模块
这是SHT40的数据手册 https://edm.eeworld.com.cn/SHT40-datasheet.pdf
简单总结来说,就是一个IIC接口的传感器,可以获取到温湿度,精度为Accuracies ΔRH = ±1.0 %RH, ΔT = ±0.1 °C
内部框图如下,是通过IIC接口来通信获取数据的
把SHT40 传感器和板子相连接
2、相关环境的准备
我们都清楚,Arduino 开发的一个优势就在于,各种驱动非常的丰富,根本不用像其他MCU一样,还要去看寄存器,去手搓底层代码。所以Arduino可以快速实现demo,验证其效果。
拿到一个模块的第一步,去Arduino驱动库翻一下,有没有现成的驱动是直接可以拿来使用的。果然有,赶紧装上,这样我们开发就是非常的快了。
3、读取温湿度代码demo
如下图操作打开示例代码,来进行研究研究
直接编译代码。报了错,我们一步一步来解决,下面的错误说明我们缺了Adafruit_BusIO库。
进行安装Adafruit_BusIO库
安装好了之后,继续编译,还是报错,还是缺少库
如下继续安装依赖库
编译通过了,但是下载后板子没有任何的反应。这时,我们就需要对示例代码进行改造了。如下所示屏蔽所有与SGP40相关的代码,
因为我们的模块中并不包含SGP40,必然初始化不过,另外还要修改的一个地方是 sht4.begin() 修改为 sht4.begin(&Wire1))即可。具体代码如下
/***************************************************************************
Example for the SGP40+SHT40 board
written by Thiago Barros for BlueDot UG (haftungsbeschränkt)
BSD License
Version 1.0.0 (2023-04-22)
This sketch was written for the sensors SGP40 and SHT40 from Sensirion and is based on the Adafruit libraries for these sensors.
The SGP40 is a device for measuring volatile organic compounds (VOC) and for evaluating indoor air quality.
The SHT40 is a digital temperature and relative humidity sensor.
The VOC Algorithm from Sensirion integrates the output values from the SHT40 to improve the calculation of the VOC index.
For more technical information on the SGP40 and on the SHT40, go to ------> http://www.bluedot.space
***************************************************************************/
// #include "Adafruit_SGP40.h"
#include "Adafruit_SHT4x.h"
// Adafruit_SGP40 sgp;
Adafruit_SHT4x sht4;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println(F("##################################"));
Serial.println(F("SGP40 test with SHT40 compensation"));
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
//Here we can configure the SHT40 Temperature and Humidity Sensor
//First we set the measurement precision
//There are three precision levels: High, Medium and Low
//The precision levels direclty affect the measurement duration, noise level and energy consumption
//On doubt, just leave it on default (High precision)
sht4.setPrecision(SHT4X_HIGH_PRECISION);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
Serial.println(F("SHT40 set to High precision"));
break;
case SHT4X_MED_PRECISION:
Serial.println(F("SHT40 set to Medium precision"));
break;
case SHT4X_LOW_PRECISION:
Serial.println(F("SHT40 set to Low precision"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
// The SHT40 has a built-in heater, which can be used for self-decontamination.
// The heater can be used for periodic creep compensation in prolongued high humidity exposure.
// For normal operation, leave the heater turned off.
sht4.setHeater(SHT4X_NO_HEATER);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
Serial.println(F("SHT40 Heater turned OFF"));
break;
case SHT4X_HIGH_HEATER_1S:
Serial.println(F("SHT40 Heater: High heat for 1 second"));
break;
case SHT4X_HIGH_HEATER_100MS:
Serial.println(F("SHT40 Heater: High heat for 0.1 second"));
break;
case SHT4X_MED_HEATER_1S:
Serial.println(F("SHT40 Heater: Medium heat for 1 second"));
break;
case SHT4X_MED_HEATER_100MS:
Serial.println(F("SHT40 Heater: Medium heat for 0.1 second"));
break;
case SHT4X_LOW_HEATER_1S:
Serial.println(F("SHT40 Heater: Low heat for 1 second"));
break;
case SHT4X_LOW_HEATER_100MS:
Serial.println(F("SHT40 Heater: Low heat for 0.1 second"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP IS OVER - LET'S CHECK THE CHIP ID!*******
// if (! sgp.begin())
// {
// Serial.println(F("SGP40 sensor not found!"));
// while (1); }
// else
// {
// Serial.print(F("SGP40 detected!\t"));
// Serial.print(F("Serial number:\t"));
// Serial.print(sgp.serialnumber[0], HEX);
// Serial.print(sgp.serialnumber[1], HEX);
// Serial.println(sgp.serialnumber[2], HEX);
// }
if (! sht4.begin(&Wire1)) {
Serial.println(F("SHT40 sensor not found!"));
while (1) ;
}
else
{
Serial.print(F("SHT40 detected!\t"));
Serial.print(F("Serial number:\t"));
Serial.println(sht4.readSerial(), HEX);
}
Serial.println(F("----------------------------------"));
}
//*********************************************************************
//*************NOW LET'S START MEASURING*******************************
void loop() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
float t = temp.temperature;
Serial.print("Temp *C = "); Serial.print(t); Serial.print("\t\t");
float h = humidity.relative_humidity;
Serial.print("Hum. % = "); Serial.print(h); Serial.print("\t\t");
// uint16_t sraw;
// sraw = sgp.measureRaw(t, h);
// Serial.print("Raw measurement: ");
// Serial.print(sraw); Serial.print("\t\t");
// int32_t voc_index;
// voc_index = sgp.measureVocIndex(t, h);
// Serial.print("Voc Index: ");
// Serial.println(voc_index);
delay(1000);
}
编译下载,就可以看到我们运行采集到的温湿度结果了
接着就是这些数据需要通过MQTT协议传输到HA中进行显示了。
根据上一节的操作,我们来配置一个HA的配置configuration.yml 。在主页上添加一下温度和湿度的显示
# Loads default set of integrations. Do not remove.
default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
mqtt:
- button:
- unique_id: arduino uno button A
name: "Arduino Click me A"
command_topic: "UNO/arduino/myButtonA/cmd_t"
payload_press: "restart"
- unique_id: arduino uno button B
name: "Arduino Click me B"
command_topic: "UNO/arduino/myButtonB/cmd_t"
payload_press: "restart"
- sensor:
- unique_id: arduino uno Time
name: "arduino Time"
state_topic: "UNO/arduino/myUptime/stat_t"
suggested_display_precision: 1
unit_of_measurement: "s"
value_template: "{{ value }}"
- unique_id: arduino uno Voltage
name: "arduino Volt"
state_topic: "UNO/arduino/myAnalogInput/stat_t"
suggested_display_precision: 3
unit_of_measurement: "V"
value_template: "{{ value }}"
- unique_id: arduino uno Temperature
name: "arduino Temperature"
state_topic: "UNO/arduino/myTemperature/stat_t"
suggested_display_precision: 3
unit_of_measurement: "°C"
value_template: "{{ value }}"
- unique_id: arduino uno Humidity
name: "arduino Humidity"
state_topic: "UNO/arduino/myHumidity/stat_t"
suggested_display_precision: 3
unit_of_measurement: "%"
value_template: "{{ value }}"
重新加载configuration.yml 后,就可以看到传感器列表中增加了两行
代码也和上一节类似。
软件流程图如下:
直接把采集到的温湿度数据上传到MQTT,具体代码如下:
#include <Adafruit_SGP40.h>
#include <Adafruit_SHT4x.h>
#include <sensirion_arch_config.h>
#include <sensirion_voc_algorithm.h>
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include <WiFiClient.h>
#include "Adafruit_SHT4x.h"
Adafruit_SHT4x sht4;
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "ChinaNet-T6zw"; //WIFI名字
char pass[] = "chu7p4s9"; //WIFI密码
int status = WL_IDLE_STATUS;
//实例化
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
const char broker[] = "192.168.1.11"; //MQTT服务器地址
int port = 1883; //MQTT服务器端口
const char topic1[] = "UNO/arduino/myTemperature/stat_t"; //发布的主题
const char topic2[] = "UNO/arduino/myHumidity/stat_t"; //发布的主题
const char mqttuser[] = "lang.you"; //MQTT服务器的用户名
const char mqttpassword[] = "123456789"; //MQTT服务器密码
//set interval for sending messages (milliseconds)
const long interval = 8000;
unsigned long previousMillis = 0;
int count = 0;
void init_sht40() {
Serial.println();
Serial.println();
Serial.println(F("##################################"));
Serial.println(F("SGP40 test with SHT40 compensation"));
//Here we can configure the SHT40 Temperature and Humidity Sensor
//First we set the measurement precision
//There are three precision levels: High, Medium and Low
//The precision levels direclty affect the measurement duration, noise level and energy consumption
//On doubt, just leave it on default (High precision)
sht4.setPrecision(SHT4X_HIGH_PRECISION);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
Serial.println(F("SHT40 set to High precision"));
break;
case SHT4X_MED_PRECISION:
Serial.println(F("SHT40 set to Medium precision"));
break;
case SHT4X_LOW_PRECISION:
Serial.println(F("SHT40 set to Low precision"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
// The SHT40 has a built-in heater, which can be used for self-decontamination.
// The heater can be used for periodic creep compensation in prolongued high humidity exposure.
// For normal operation, leave the heater turned off.
sht4.setHeater(SHT4X_NO_HEATER);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
Serial.println(F("SHT40 Heater turned OFF"));
break;
case SHT4X_HIGH_HEATER_1S:
Serial.println(F("SHT40 Heater: High heat for 1 second"));
break;
case SHT4X_HIGH_HEATER_100MS:
Serial.println(F("SHT40 Heater: High heat for 0.1 second"));
break;
case SHT4X_MED_HEATER_1S:
Serial.println(F("SHT40 Heater: Medium heat for 1 second"));
break;
case SHT4X_MED_HEATER_100MS:
Serial.println(F("SHT40 Heater: Medium heat for 0.1 second"));
break;
case SHT4X_LOW_HEATER_1S:
Serial.println(F("SHT40 Heater: Low heat for 1 second"));
break;
case SHT4X_LOW_HEATER_100MS:
Serial.println(F("SHT40 Heater: Low heat for 0.1 second"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP IS OVER - LET'S CHECK THE CHIP ID!*******
// if (! sgp.begin())
// {
// Serial.println(F("SGP40 sensor not found!"));
// while (1); }
// else
// {
// Serial.print(F("SGP40 detected!\t"));
// Serial.print(F("Serial number:\t"));
// Serial.print(sgp.serialnumber[0], HEX);
// Serial.print(sgp.serialnumber[1], HEX);
// Serial.println(sgp.serialnumber[2], HEX);
// }
if (!sht4.begin(&Wire1)) {
Serial.println(F("SHT40 sensor not found!"));
while (1)
;
} else {
Serial.print(F("SHT40 detected!\t"));
Serial.print(F("Serial number:\t"));
Serial.println(sht4.readSerial(), HEX);
}
Serial.println(F("----------------------------------"));
}
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
init_sht40();
// attempt to connect to Wifi network:
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
// failed, retry
Serial.print(".");
delay(5000);
}
Serial.println("You're connected to the network");
Serial.println();
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword(mqttuser, mqttpassword);
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
delay(1000);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
}
void loop() {
// call poll() regularly to allow the library to send MQTT keep alive which
// avoids being disconnected by the broker
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp); // populate temp and humidity objects with fresh data
float t = temp.temperature;
Serial.print("Temp *C = ");
Serial.print(t);
Serial.print("\t\t");
float h = humidity.relative_humidity;
Serial.print("Hum. % = ");
Serial.print(h);
Serial.print("\t\t");
mqttClient.poll();
mqttClient.beginMessage(topic1);
mqttClient.print(t); //把读取的温度值进行上传到MQTT服务器
mqttClient.endMessage();
mqttClient.beginMessage(topic2);
mqttClient.print(h); //把读取的湿度值进行上传到MQTT服务器
mqttClient.endMessage();
delay(10);
}
在HA界面上可以看到采集到的温湿度值一直在更新
4、对参加本次活动的心得体会
通过参加本次 Follow me 第二季第2期任务 ,学会使用了Arduino UNO R4 WiFi 板子,也学会了HA以及搭建MQTT服务器。
同时也看到了使用Arduino开发的快捷性,为之后工作提供了验证思路,比如某一些传感器可以使用Arduino+现有的库进行快速实现demo,验证效果。
另外,通过本次的学习,了解到了HA,后续我也会在家里慢慢通过HA+MQTT实现家里一些电子设备的控制。后面也尝试将HA和EMQ部署上树莓派上,实现一个小的服务器。
非常的感谢DigiKey和EEWorld发起的大型开发板体验活动,让我学会了很多技术知识。后面我也会积极参加社区里这样的活动,和网友在微信群以及论坛里交流技术很开心。
期望后续Follow me 可以出一些linux的小板子,实现一些小的功能。或者出一些MCU系列的板子,使用C语言编程的,可以评估板子性能的,期望能出NXP、瑞萨、英飞凌等的。
5、可编译下载的代码
可编译的代码已将上传到资源,具体可以参考这个链接。
https://download.eeworld.com.cn/detail/cqut%E9%9D%A2%E7%A0%81/634530
6、附录上完成的各个任务贴
入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!:
https://bbs.eeworld.com.cn/thread-1293710-1-1.html
基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线:
https://bbs.eeworld.com.cn/thread-1294700-1-1.html
进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant):
https://bbs.eeworld.com.cn/thread-1295058-1-1.html
扩展任务二:通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据:
https://bbs.eeworld.com.cn/thread-1295288-1-1.html
-
加入了学习《【Follow me 第二季第2期任务】 各个任务实现的展示效果》,观看 【Follow me 第二季第2期任务】通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据
-
发表了主题帖:
【Follow me第二季第2期】+ SHT40温湿度数据上次HA,并在HA面板显示
本帖最后由 cqut面码 于 2024-9-30 22:03 编辑
1、使用到的硬件介绍
这次实验的主要实现目标是通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据。想了解一下SHT40这个模块
这是SHT40的数据手册 https://edm.eeworld.com.cn/SHT40-datasheet.pdf
简单总结来说,就是一个IIC接口的传感器,可以获取到温湿度,精度为Accuracies ΔRH = ±1.0 %RH, ΔT = ±0.1 °C
内部框图如下,是通过IIC接口来通信获取数据的
把SHT40 传感器和板子相连接
2、相关环境的准备
我们都清楚,Arduino 开发的一个优势就在于,各种驱动非常的丰富,根本不用像其他MCU一样,还要去看寄存器,去手搓底层代码。所以Arduino可以快速实现demo,验证其效果。
拿到一个模块的第一步,去Arduino驱动库翻一下,有没有现成的驱动是直接可以拿来使用的。果然有,赶紧装上,这样我们开发就是非常的快了。
3、读取温湿度代码demo
如下图操作打开示例代码,来进行研究研究。
直接编译代码。报了错,我们一步一步来解决,下面的错误说明我们缺了Adafruit_BusIO库。
进行安装Adafruit_BusIO库
安装好了之后,继续编译,还是报错,还是缺少库
如下继续安装依赖库
编译通过了,但是下载后板子没有任何的反应。这时,我们就需要对示例代码进行改造了。如下所示屏蔽所有与SGP40相关的代码,
因为我们的模块中并不包含SGP40,必然初始化不过,另外还要修改的一个地方是 sht4.begin() 修改为 sht4.begin(&Wire1))即可。具体代码如下:
/***************************************************************************
Example for the SGP40+SHT40 board
written by Thiago Barros for BlueDot UG (haftungsbeschränkt)
BSD License
Version 1.0.0 (2023-04-22)
This sketch was written for the sensors SGP40 and SHT40 from Sensirion and is based on the Adafruit libraries for these sensors.
The SGP40 is a device for measuring volatile organic compounds (VOC) and for evaluating indoor air quality.
The SHT40 is a digital temperature and relative humidity sensor.
The VOC Algorithm from Sensirion integrates the output values from the SHT40 to improve the calculation of the VOC index.
For more technical information on the SGP40 and on the SHT40, go to ------> http://www.bluedot.space
***************************************************************************/
// #include "Adafruit_SGP40.h"
#include "Adafruit_SHT4x.h"
// Adafruit_SGP40 sgp;
Adafruit_SHT4x sht4;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println(F("##################################"));
Serial.println(F("SGP40 test with SHT40 compensation"));
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
//Here we can configure the SHT40 Temperature and Humidity Sensor
//First we set the measurement precision
//There are three precision levels: High, Medium and Low
//The precision levels direclty affect the measurement duration, noise level and energy consumption
//On doubt, just leave it on default (High precision)
sht4.setPrecision(SHT4X_HIGH_PRECISION);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
Serial.println(F("SHT40 set to High precision"));
break;
case SHT4X_MED_PRECISION:
Serial.println(F("SHT40 set to Medium precision"));
break;
case SHT4X_LOW_PRECISION:
Serial.println(F("SHT40 set to Low precision"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
// The SHT40 has a built-in heater, which can be used for self-decontamination.
// The heater can be used for periodic creep compensation in prolongued high humidity exposure.
// For normal operation, leave the heater turned off.
sht4.setHeater(SHT4X_NO_HEATER);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
Serial.println(F("SHT40 Heater turned OFF"));
break;
case SHT4X_HIGH_HEATER_1S:
Serial.println(F("SHT40 Heater: High heat for 1 second"));
break;
case SHT4X_HIGH_HEATER_100MS:
Serial.println(F("SHT40 Heater: High heat for 0.1 second"));
break;
case SHT4X_MED_HEATER_1S:
Serial.println(F("SHT40 Heater: Medium heat for 1 second"));
break;
case SHT4X_MED_HEATER_100MS:
Serial.println(F("SHT40 Heater: Medium heat for 0.1 second"));
break;
case SHT4X_LOW_HEATER_1S:
Serial.println(F("SHT40 Heater: Low heat for 1 second"));
break;
case SHT4X_LOW_HEATER_100MS:
Serial.println(F("SHT40 Heater: Low heat for 0.1 second"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP IS OVER - LET'S CHECK THE CHIP ID!*******
// if (! sgp.begin())
// {
// Serial.println(F("SGP40 sensor not found!"));
// while (1); }
// else
// {
// Serial.print(F("SGP40 detected!\t"));
// Serial.print(F("Serial number:\t"));
// Serial.print(sgp.serialnumber[0], HEX);
// Serial.print(sgp.serialnumber[1], HEX);
// Serial.println(sgp.serialnumber[2], HEX);
// }
if (! sht4.begin(&Wire1)) {
Serial.println(F("SHT40 sensor not found!"));
while (1) ;
}
else
{
Serial.print(F("SHT40 detected!\t"));
Serial.print(F("Serial number:\t"));
Serial.println(sht4.readSerial(), HEX);
}
Serial.println(F("----------------------------------"));
}
//*********************************************************************
//*************NOW LET'S START MEASURING*******************************
void loop() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
float t = temp.temperature;
Serial.print("Temp *C = "); Serial.print(t); Serial.print("\t\t");
float h = humidity.relative_humidity;
Serial.print("Hum. % = "); Serial.print(h); Serial.print("\t\t");
// uint16_t sraw;
// sraw = sgp.measureRaw(t, h);
// Serial.print("Raw measurement: ");
// Serial.print(sraw); Serial.print("\t\t");
// int32_t voc_index;
// voc_index = sgp.measureVocIndex(t, h);
// Serial.print("Voc Index: ");
// Serial.println(voc_index);
delay(1000);
}
编译下载,就可以看到我们运行采集到的温湿度结果了
接着就是这些数据需要通过MQTT协议传输到HA中进行显示了。
根据上一节的操作,我们来配置一个HA的配置configuration.yml 。在主页上添加一下温度和湿度的显示
# Loads default set of integrations. Do not remove.
default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
mqtt:
- button:
- unique_id: arduino uno button A
name: "Arduino Click me A"
command_topic: "UNO/arduino/myButtonA/cmd_t"
payload_press: "restart"
- unique_id: arduino uno button B
name: "Arduino Click me B"
command_topic: "UNO/arduino/myButtonB/cmd_t"
payload_press: "restart"
- sensor:
- unique_id: arduino uno Time
name: "arduino Time"
state_topic: "UNO/arduino/myUptime/stat_t"
suggested_display_precision: 1
unit_of_measurement: "s"
value_template: "{{ value }}"
- unique_id: arduino uno Voltage
name: "arduino Volt"
state_topic: "UNO/arduino/myAnalogInput/stat_t"
suggested_display_precision: 3
unit_of_measurement: "V"
value_template: "{{ value }}"
- unique_id: arduino uno Temperature
name: "arduino Temperature"
state_topic: "UNO/arduino/myTemperature/stat_t"
suggested_display_precision: 3
unit_of_measurement: "°C"
value_template: "{{ value }}"
- unique_id: arduino uno Humidity
name: "arduino Humidity"
state_topic: "UNO/arduino/myHumidity/stat_t"
suggested_display_precision: 3
unit_of_measurement: "%"
value_template: "{{ value }}"
重新加载configuration.yml 后,就可以看到传感器列表中增加了两行
代码也和上一节类似。直接把采集到的温湿度数据上传到MQTT,具体代码如下:
#include <Adafruit_SGP40.h>
#include <Adafruit_SHT4x.h>
#include <sensirion_arch_config.h>
#include <sensirion_voc_algorithm.h>
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include <WiFiClient.h>
#include "Adafruit_SHT4x.h"
Adafruit_SHT4x sht4;
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "ChinaNet-T6zw"; //WIFI名字
char pass[] = "chu7p4s9"; //WIFI密码
int status = WL_IDLE_STATUS;
//实例化
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
const char broker[] = "192.168.1.11"; //MQTT服务器地址
int port = 1883; //MQTT服务器端口
const char topic1[] = "UNO/arduino/myTemperature/stat_t"; //发布的主题
const char topic2[] = "UNO/arduino/myHumidity/stat_t"; //发布的主题
const char mqttuser[] = "lang.you"; //MQTT服务器的用户名
const char mqttpassword[] = "123456789"; //MQTT服务器密码
//set interval for sending messages (milliseconds)
const long interval = 8000;
unsigned long previousMillis = 0;
int count = 0;
void init_sht40() {
Serial.println();
Serial.println();
Serial.println(F("##################################"));
Serial.println(F("SGP40 test with SHT40 compensation"));
//Here we can configure the SHT40 Temperature and Humidity Sensor
//First we set the measurement precision
//There are three precision levels: High, Medium and Low
//The precision levels direclty affect the measurement duration, noise level and energy consumption
//On doubt, just leave it on default (High precision)
sht4.setPrecision(SHT4X_HIGH_PRECISION);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
Serial.println(F("SHT40 set to High precision"));
break;
case SHT4X_MED_PRECISION:
Serial.println(F("SHT40 set to Medium precision"));
break;
case SHT4X_LOW_PRECISION:
Serial.println(F("SHT40 set to Low precision"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP - SAFE TO IGNORE!************************
// The SHT40 has a built-in heater, which can be used for self-decontamination.
// The heater can be used for periodic creep compensation in prolongued high humidity exposure.
// For normal operation, leave the heater turned off.
sht4.setHeater(SHT4X_NO_HEATER);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
Serial.println(F("SHT40 Heater turned OFF"));
break;
case SHT4X_HIGH_HEATER_1S:
Serial.println(F("SHT40 Heater: High heat for 1 second"));
break;
case SHT4X_HIGH_HEATER_100MS:
Serial.println(F("SHT40 Heater: High heat for 0.1 second"));
break;
case SHT4X_MED_HEATER_1S:
Serial.println(F("SHT40 Heater: Medium heat for 1 second"));
break;
case SHT4X_MED_HEATER_100MS:
Serial.println(F("SHT40 Heater: Medium heat for 0.1 second"));
break;
case SHT4X_LOW_HEATER_1S:
Serial.println(F("SHT40 Heater: Low heat for 1 second"));
break;
case SHT4X_LOW_HEATER_100MS:
Serial.println(F("SHT40 Heater: Low heat for 0.1 second"));
break;
}
//*********************************************************************
//*************ADVANCED SETUP IS OVER - LET'S CHECK THE CHIP ID!*******
// if (! sgp.begin())
// {
// Serial.println(F("SGP40 sensor not found!"));
// while (1); }
// else
// {
// Serial.print(F("SGP40 detected!\t"));
// Serial.print(F("Serial number:\t"));
// Serial.print(sgp.serialnumber[0], HEX);
// Serial.print(sgp.serialnumber[1], HEX);
// Serial.println(sgp.serialnumber[2], HEX);
// }
if (!sht4.begin(&Wire1)) {
Serial.println(F("SHT40 sensor not found!"));
while (1)
;
} else {
Serial.print(F("SHT40 detected!\t"));
Serial.print(F("Serial number:\t"));
Serial.println(sht4.readSerial(), HEX);
}
Serial.println(F("----------------------------------"));
}
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
init_sht40();
// attempt to connect to Wifi network:
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
// failed, retry
Serial.print(".");
delay(5000);
}
Serial.println("You're connected to the network");
Serial.println();
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword(mqttuser, mqttpassword);
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
delay(1000);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
}
void loop() {
// call poll() regularly to allow the library to send MQTT keep alive which
// avoids being disconnected by the broker
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp); // populate temp and humidity objects with fresh data
float t = temp.temperature;
Serial.print("Temp *C = ");
Serial.print(t);
Serial.print("\t\t");
float h = humidity.relative_humidity;
Serial.print("Hum. % = ");
Serial.print(h);
Serial.print("\t\t");
mqttClient.poll();
mqttClient.beginMessage(topic1);
mqttClient.print(t); //把读取的温度值进行上传到MQTT服务器
mqttClient.endMessage();
mqttClient.beginMessage(topic2);
mqttClient.print(h); //把读取的湿度值进行上传到MQTT服务器
mqttClient.endMessage();
delay(10);
}
在HA界面上可以看到采集到的温湿度值一直在更新
4、展示效果和源码附件
-
上传了资料:
【Follow me 第二季第2期任务】4个任务实现的源码
-
加入了学习《【Follow me 第二季第2期任务】 各个任务实现的展示效果》,观看 【Follow me 第二季第2期任务】Blink / 串口打印Hello EEWorld!
-
加入了学习《【Follow me 第二季第2期任务】 各个任务实现的展示效果》,观看 【Follow me 第二季第2期任务】MQTT接入到HomeAssistant
-
加入了学习《【Follow me 第二季第2期任务】 各个任务实现的展示效果》,观看 【Follow me 第二季第2期任务】驱动12x8点阵LED;用DAC生成正弦波;OPAMP放大DAC信号;用ADC采集并上传波形
- 2024-09-28
-
加入了学习《FollowMe 第二季:2 - Arduino UNO R4 Wi-Fi 及任务讲解》,观看 Arduino UNO R4 Wi-Fi 及任务讲解
-
发表了主题帖:
【Follow me第二季第2期】MQTT协议接入到开源的智能家居平台HA
本帖最后由 cqut面码 于 2024-9-29 22:06 编辑
一、Home Assistant 的安装
为了方便操作,这里直接在电脑上使用Docker进行安装。
官网上下载 https://www.docker.com/products/docker-desktop/ Docker安装包。
安装好了Docker之后,就需要在Docker中拉取Home Assistant 镜像了。可以参考大佬博主的一篇文章,有详细的介绍命令行的使用,我这里只是展示效果了。
https://bbs.eeworld.com.cn/thread-1293807-1-1.html
(1) 第一步先用cmd 搜索镜像
docker search home-assistant
(2)第二步进行拉取最新的镜像
docker pull homeassistant/home-assistant
漫长的等Docker中拉取Home Assistant镜像中。
(3)第三步进行进行创建容器
其中HA_container为可自定义的容器名称,D:/ProgramData/HA为可自定义配置保存路径(注意是右斜杠)
docker run -d --name="HA_container" -v D:/ProgramData/HA:/config -p 8123:8123 homeassistant/home-assistant
(4)最后一步在浏览器中输入 http://localhost:8123/ 就可看到成功了。
接着注册用户,进入主页就好了,具体如下:
二、EMQX 的安装
(1)第一步在Docker中拉取EMQX镜像
CMD中输入命令行,选择第一个进行安装
docker search emqx
docker pull emqx
然后又是漫长的拉取等待
(2)第二步创建EMQX容器
cmd输入以下命令,其中HA为可自定义的容器名称,D:/ProgramData/HA为可自定义配置保存路径(注意是右斜杠)
(3)第三步测试EMQX是否OK
浏览器中输入网址 http://localhost:18083/ 默认账号:admin 默认密码:public,成功的界面如下
(4)设置EMQ
一路默认创建我们的内置数据库
再创建一个用户
然后打开docker 就可以看到我们安装的 home assistant 和 EMQ
(5)然后进行EMQ和HA之间的连接
找到EMQ的集群概览。找到节点的信息,如下
打开HA,设置添加MQTT
代理名字填前面在EMQ中获取到的地址,然后创建就OK了。
回到EMQ,可以发现HA已经通过MQTT接入可EMQ,说明HA与EMQ已经成功链接了。
三、实现WIFI联网功能
一步一来做,想要MQTT上传数据,就需要先学会怎么把板子通过WIFI连接上网络。
直接如下面所示,使用开发板的官方示例来进行连接。我们只要修改相应的参数即可了
/*
This example connects to an unencrypted WiFi network.
Then it prints the MAC address of the WiFi module,
the IP address obtained, and other network details.
created 13 July 2010
by dlf (Metodo2 srl)
modified 31 May 2012
by Tom Igoe
Find the full UNO R4 WiFi Network documentation here:
https://docs.arduino.cc/tutorials/uno-r4-wifi/wifi-examples#connect-with-wpa
*/
#include <WiFiS3.h>
#include "arduino_secrets.h"
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS; // the WiFi radio's status
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
// don't continue
while (true);
}
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println("Please upgrade the firmware");
}
// attempt to connect to WiFi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
// you're connected now, so print out the data:
Serial.print("You're connected to the network");
printCurrentNet();
printWifiData();
}
void loop() {
// check the network connection once every 10 seconds:
delay(10000);
printCurrentNet();
}
void printWifiData() {
// print your board's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print your MAC address:
byte mac[6];
WiFi.macAddress(mac);
Serial.print("MAC address: ");
printMacAddress(mac);
}
void printCurrentNet() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print the MAC address of the router you're attached to:
byte bssid[6];
WiFi.BSSID(bssid);
Serial.print("BSSID: ");
printMacAddress(bssid);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.println(rssi);
// print the encryption type:
byte encryption = WiFi.encryptionType();
Serial.print("Encryption Type:");
Serial.println(encryption, HEX);
Serial.println();
}
void printMacAddress(byte mac[]) {
for (int i = 0; i < 6; i++) {
if (i > 0) {
Serial.print(":");
}
if (mac[i] < 16) {
Serial.print("0");
}
Serial.print(mac[i], HEX);
}
Serial.println();
}
唯一的修改点就是把 SECRET_SSID 和 SECRET_PASS分别替换为自己家里的WIFI名称和密码。注意,不要使用5G频段的WIFI,使用2.4G的WIFI,5G频段WIFI连接不上,应该是硬件不支持的原因。
修改如下:
#define SECRET_SSID "ChinaNet-T6zw"
#define SECRET_PASS "chu7p4s9"
编译下载,打开串口监视器,可以看到已经获取到了MAC地址和WIFI的强度,说明连接成功了
四、MQTT上传数据的准备工作
接着就需要开始准备MQTT相关的东西了
(1) MQTT BOX 的下载准备
MQTTBUX是一个客户端,方便我们调试的时候看MQTT发布的数据,下载链接如下:
https://apps.microsoft.com/detail/9nblggh55jzg?hl=zh-mo&gl=MO
安装之后,就进行一些基本的配置就可以使用了。
MQTT服务器的账户和密码就是我们前面设置的,服务器地址就是我们电脑的本机IP地址。EMQ(MQTT服务器)是通过docker安装在自己本地电脑上的,所有地址是本机电脑地址,直接使用ipconfig 命令可查。如果是把EMQ装在了树莓派上,需要填写树莓派的IP会地址。
设置好之后直接订阅一个主题就好了
这里我们订阅了这个主题 UNO/arduino/myAnalogInput/stat_t,等会开发板向这个主题发送数据,这个客户端就可以收到。
(2) Arduino IDE 安装 ArduinoMqttClient 库
具体如下图
五、代码实战实现数据上MQTT
实现了通过ADC A3接口来读取A0接口 DAC输出sin信号的电压值,然后把这个值通过MQTT协议发布出去,
实际的物理连接如下:
具体的实现代码如下
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include <WiFiClient.h>
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "ChinaNet-T6zw"; //WIFI名字
char pass[] = "chu7p4s9"; //WIFI密码
int status = WL_IDLE_STATUS;
//实例化
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
analogWave wave(DAC); //实例化一个对象
const char broker[] = "192.168.1.11"; //MQTT服务器地址
int port = 1883; //MQTT服务器端口
const char topic[] = "UNO/arduino/myAnalogInput/stat_t"; //发布的主题
const char mqttuser[] = "lang.you"; //MQTT服务器的用户名
const char mqttpassword[] = "123456789"; //MQTT服务器密码
//set interval for sending messages (milliseconds)
const long interval = 8000;
unsigned long previousMillis = 0;
int count = 0;
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(2000000);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
//A0 DAC 输出一个波形
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
// 设置ADC的分辨率
analogReadResolution(14); //change to 14-bit resolution
// attempt to connect to Wifi network:
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
// failed, retry
Serial.print(".");
delay(5000);
}
Serial.println("You're connected to the network");
Serial.println();
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword(mqttuser, mqttpassword);
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
delay(1000);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
}
void loop() {
// call poll() regularly to allow the library to send MQTT keep alive which
// avoids being disconnected by the broker
mqttClient.poll();
//通过ADC采集 A0上输出的sin波形
float Rvalue = analogRead(A3) * 4.7/16383; //读取A0引脚的值 A3和 A0连接 14bit ADC采样
mqttClient.beginMessage(topic);
mqttClient.print(Rvalue); //把读取的值进行上传到MQTT服务器
mqttClient.endMessage();
delay(10);
}
查看MQTT BOX,并订阅UNO/arduino/myAnalogInput/stat_t主题下面的数据,可以看到数据一直在推送刷新
打开HA查看上传的电压数据是一直在刷新的
再来看看我们实际的HA上显示的sin信号的电压值
这里为什么我的HA会有这个值的显示呢,其实可以参考直播大佬的配置,详情可见下面链接。实际配置了configuration.yaml文件,增加以下代码保存。
https://bbs.eeworld.com.cn/thread-1293807-1-1.html
mqtt:
- button:
- unique_id: arduino uno button A
name: "Arduino Click me A"
command_topic: "UNO/arduino/myButtonA/cmd_t"
payload_press: "restart"
- unique_id: arduino uno button B
name: "Arduino Click me B"
command_topic: "UNO/arduino/myButtonB/cmd_t"
payload_press: "restart"
- sensor:
- unique_id: arduino uno Time
name: "arduino Time"
state_topic: "UNO/arduino/myUptime/stat_t"
suggested_display_precision: 1
unit_of_measurement: "s"
value_template: "{{ value }}"
- unique_id: arduino uno Voltage
name: "arduino Volt"
state_topic: "UNO/arduino/myAnalogInput/stat_t"
suggested_display_precision: 3
unit_of_measurement: "V"
value_template: "{{ value }}"
再来看看EMQ的情况
实际显示了3设备连接,一个就是我们板子,一个HA,一个是MQTT BOX,三个都作为了MQTT的客户端连接到了MQTT服务器
六、视频展示和最终工程代码代码
- 2024-09-24
-
发表了主题帖:
【Follow me第二季第2期】+ 基础任务 点阵LED+DAC正弦波+OPAMP+波形显示
本帖最后由 cqut面码 于 2024-9-30 00:39 编辑
1、前言
前面学习了环境的搭建、LED的点亮翻转、以及串口的输出。接着就来学习基础任务里面的一些内容, 驱动12x8点阵LED和用DAC生成正弦波。
2、 驱动12x8点阵LED
先放官方的链接https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/,官方给出了详细的这个LED矩阵点亮的实现方式,我们可以通过官方的文档来进行学习,
并完成这个矩阵LED的实验,嵌入式就是得多看看官方资料,不要一个人闭门造车。快速实现了,再去了解原理,深入消化,才不会打击学习的积极性。
官方已经给出一个驱动点了这个矩阵LED库函数,我们只需要包含使用就好了。如下是输出一个爱心的框图,详细的各个功能都在代码中进行了注释说明
核心思想就是通过把自己要显示的LED矩阵相关的数据,直接写入就好,实际就是通过IO通过LED矩阵的亮灭。
画出一个心形的代码如下:
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
ArduinoLEDMatrix led_matrix; //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际效果如下:
3、 用DAC生成正弦波
Arduino UNO R4 WiFi 内置DAC(数模转换器),用于将数字信号转换为模拟信号。这里我们直接用DAC输出一个正弦波,然后用示波器去观察器输出的波形
先看原理图DAC的输出口是哪个,从原理图上可以看到,开发板上引出的DAC引脚为A0,等会我们直接用示波器测量A0即可。
然后,参考官网的代码 https://docs.arduino.cc/tutorials/uno-r4-wifi/dac/,完成DAC正弦波的输出,代码我们在前面的基础上继续完善,具体如下:
这里只是生成可一个50HZ的sin波形并没有在循环中去改变频率或者波形等其他进操作
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际示波器测试的效果如下,可以看出最大值为4.6v,最小值为-13mv,频率为49.9HZ左右,波形确实比较难看,锯齿状态还是很严重的,按理说这个板子的12位的DAC性能应不止这样,
应该是内部封装的效果导致一个周期数据输出太少的原因,看来还是得上IDF编程才行。
另外还有一个问题就是,为啥最大值为4.6V,而不是5V,我怀疑是设计缺陷,DAC的参考电压不准,不是5V。时间原因就不去验证了
4、 使用OPAMP对上面的DAC信号放大
OPAMP 实际用处还是很大的,在性能要求不高的情况下,直接使用板载的OPAMP进行放大信号,可以节约成本,避免外部在接一个运放。
当然OPAMP还有一个作用就是做电压跟随器,电压跟随器具有高输入阻抗和低输出阻抗——这是它们缓冲作用的本质,它们增强信号,从而允许高阻抗源驱动低阻抗负载。
这里我们就只展示放大的作用。具官方文档可以参考学习https://docs.arduino.cc/tutorials/uno-r4-wifi/opamp
从前面的实验已经看出了,直接输出的波形已经幅度值已经达到了4.7V,这已经是DAC输出的最大值了。如果我们再在这个基础上去放大这个信号,无疑就会超过4.7V。
就会出现两种情况 1、会出现削波 2、可能损坏电路板。为了后续放大实验方便,我们限制一下幅度值,输出到最大值的1/4即可,便于后续放大波形:
代码很简单,实际就是在setup中多了一个对波形幅值的限制, wave.amplitude(0.25); //设置幅度值为4.7 的1/4
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
实际示波器实测效果如果下,还是挺符合4.7V的 1/4 波形的。
好了,有了基础波形,我们就来搭建运放把。额,为了搭建运放的电路,我去找了好久的电阻,建议下次不搞这种,薄膜电阻真的很难找呀,还得找块面包板。
运算放大器放大电路示意图如下所示:
实际用于两个10K的电阻和洞洞板搭建的电路图如下所示:
下面是搭建了2倍放大后的示波器效果图:黄色为放大前的波形图,蓝色为经过放大后的波形图。
最后附上本次实验的代码:
相对于前面而言,只是添加了一句代码。打开了OPAMP
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
#include <OPAMP.h>
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(115200);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
5、上位机波形展示
我们使用A4 和 A5 引脚分别采集DAC的输出,和经过OPAMP放大之后的信号输出。然后通过串口发送到上位机进行波形的展示。
使用了 analogReadResolution(14); //change to 14-bit resolution 配置ADC的分辨率最高为14bit
使用了 float Rvalue1 = analogRead(A4) * 4.7/16383; //读取A4的引脚电压
实际的代码如下:
#include "Arduino_LED_Matrix.h" /*包含官方的驱动库*/
#include "analogWave.h" // 包含波形输出的模拟文件
#include <OPAMP.h>
ArduinoLEDMatrix led_matrix; //实例化一个对象
analogWave wave(DAC); //实例化一个对象
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
/* 爱心的框图 */
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
// put your setup code here, to run once:
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //开启放大器
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
Serial.begin(2000000);
led_matrix.begin(); //初始化启动这个对象
led_matrix.renderBitmap(frame, 8, 12); //把LED矩阵的数据写入
wave.sine(50); //产生一个50HZ的sin波形
wave.amplitude(0.25); //设置幅度值为4.7 的1/4
//配置ADC的分辨率最高为14bit
analogReadResolution(14); //change to 14-bit resolution
}
void loop() {
float Rvalue1 = analogRead(A4) * 4.7/16383; //读取A4的引脚电压
float Rvalue2 = analogRead(A5) * 4.7/16383; //读取A5的引脚电压
Serial.print(Rvalue1);
Serial.print(",");
Serial.println(Rvalue2);
delay(2);
}
最后我们把我们的波形在IDE上自带的串口绘图仪展示出来,可以看到基本上波形2是波形1的2倍数据关系。
这个实验中,为了方便展示波形,我们把串口的波特率调整到了最高
6、源代码以及展示视频
- 2024-09-14
-
加入了学习《串口打印Hello EEWorld!》,观看 串口打印
- 2024-09-11
-
发表了主题帖:
【Follow me第二季第2期】+ 入门任务 搭建环境、Blink 、串口打印输出
本帖最后由 cqut面码 于 2024-9-30 00:43 编辑
一、环境的搭建
首先,根据自己电脑的操作系统从官网上下载 Arduino IDE https://www.arduino.cc,进行安装。然后打开Arduino IDE,把Arduino UNO R4 开发板通过type-c 线连接到电脑上。此时,IDE上如下图1中需要选择串口连接上的开发板版本型号,右下角会自动弹出安装对应的软件包,如下图2,点击等待安装完成即可。
图1.选择连接Arduino UNO R4开发板
图2.等待软件包的安装
二、运行Blink
点击文件,新建项目,然后进行保存。第一个我们要实现的是点灯,没错 哈哈哈哈,一个合格的嵌入式工程师就是得会各种点灯。
打开 Arduino UNO R4 的原理图,如下图3,可以看到LED的引脚是P102。
图3. LED引脚控制图
由原理图可知,直接控制P102 高低就可以实现DL4 灯进行翻转。根据Arduino UNO R4的引脚图,图4,可以知道P102是开发板子的D13。
图4. 开发板引脚图
所以代码中直接控制D13就可以了。具体代码如下,主要就是500ms去翻转一次电平,每次翻转前先读取电平,然后再取反电平进行翻转
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
}
三、串口输出
官方文档告诉了D0 和D1 被用于USB的串口输入和输出。所以不需要我们再进行串口0的IO配置,直接拿来使用就可以
首先初始化设置波特率为115200,然后直接在loop循环函数中每500ms输出一次串口数据,具体代码如下:
/*串口初始化函数*/
void uart_init(uint32_t baud)
{
Serial.begin(baud);
}
void setup() {
// put your setup code here, to run once:
pinMode(13, OUTPUT); /* 设置13引脚为输出模式 */
digitalWrite(13, HIGH); /* 默认输出为高*/
uart_init(115200); /* 串口初始化 */
}
void loop() {
// put your main code here, to run repeatedly:
delay(500);
digitalWrite(13, !digitalRead(13)); /* 每500ms进行一次LED灯的翻转*/
Serial.println("Hello EEWorld !");
}
然后打开串口监视器,设置好波特率为115200,即可看到串口监视器每500ms输出一帧串口数据了。
四、展示视频和源码文件