- 2025-01-13
-
发表了主题帖:
【Follow me第二季第3期】+项目总结
本帖最后由 superw 于 2025-1-13 12:37 编辑
一、视频介绍
二、任务实现详情
入门任务:搭建环境,下载调试示例程序,Blink,按键
物料清单:EK-RA6M5
设计思路:
根据出厂示例程序,通过按键外部中断控制LED灯的亮度(占空比)和闪烁快慢(频率),将按键的GPIO配置为外部中断模式,每当按下时跳转到中断服务函数中,进行占空比和频率的切换。按键默认状态下被外部电阻上拉,当按下按键时,IO为低电平,因此要配置为下降沿触发中断。
对于占空比和频率的输出,通过配置内部定时器实现输出。
软件流程图:
代码片段:
/**********************************************************************************************************************
* Function Name: button_irq10_callback
* Description : SW1 Interrupt handler.
* Argument : p_args
* Return Value : None
*********************************************************************************************************************/
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) % 5);
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) % 5);
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);
}
}
}
/**********************************************************************************************************************
End of function button_irq9_callback
*********************************************************************************************************************/
uint32_t g_pwm_dcs[5] =
{ LED_INTENSITY_10, LED_INTENSITY_30, LED_INTENSITY_50, LED_INTENSITY_70, LED_INTENSITY_90 };
uint32_t g_pwm_rates[5] =
{ BLINK_FREQ_1HZ, BLINK_FREQ_3HZ, BLINK_FREQ_5HZ, BLINK_FREQ_7HZ, BLINK_FREQ_10HZ };
st_board_status_t g_board_status =
{ };
uint8_t g_pwm_dcs_data[] =
{ 10, 30, 50, 70, 90 };
uint8_t g_pwm_rates_data[] =
{ 1, 3, 5, 7, 10 };
功能展示:
基础任务:quad-spi flash和octo-spi flash配置及读写速度测试;DAC配置生成波形及性能测试
物料清单:EK-RA6M5
设计思路:
EK-RA6M5板载有QSPI和OSPI存储器,QSPI Flash对应型号为MX25L25645G,采用四线SPI模式操作;OSPI Flash对应型号为MX25LM51245GM,采用八线SPI模式操作。开发板上内部已连接好线路,无需额外硬件连接。
关于QSPI的引脚和外设配置如下
关于OSPI的引脚和外设配置如下
同时,官方针对EK-RA6M5提供的fsp开发包里面也有关于QSPI和OSPI的相关例程,可以进行学习。
RA6M5芯片具有一个DAC外设,两个通道,可以分别输出不同的电压值或者波形。关于DAC的引脚和外设配置如下
软件流程图:
代码片段:
block_size_actual = value;
if ((MENU_ENTER_RESPONSE_CRTL == c) && (0 != block_size_actual))
{
fsp_err_t fsp_err;
uint32_t ospi_read_result = 0;
uint32_t ospi_write_result = 0;
uint32_t qspi_read_result = 0;
uint32_t qspi_write_result = 0;
fsp_err = R_GPT_Open(g_memory_performance.p_ctrl, g_memory_performance.p_cfg);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
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);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\n-------------------------------------------------");
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\nOperation/Flash Quad-SPI Octa-SPI");
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\n-------------------------------------------------");
sprintf(s_print_buffer, "\r\nWrite %6ld %6ld", qspi_write_result , ospi_write_result);
/* 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, "\r\nRead %6ld %6ld", qspi_read_result , ospi_read_result);
/* 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-------------------------------------------------");
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\nNote: Times are in microseconds");
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);
}
while ((CONNECTION_ABORT_CRTL != c))
{
if ((MENU_EXIT_CRTL == c) || (0x00 == c))
{
break;
}
c = input_from_console();
}
功能展示:
分别对QSPI和OSPI写入32KB数据并读出,测试结果如下
进阶任务:示例程序中新增命令打印信息
物料清单:EK-RA6M5
设计思路:
官方提供的出厂demo中已经预置了7条测试命令,包括USB、以太网等等,对于新增命令打印信息,找到官方实现的函数,依葫芦画瓢即可。
软件流程图:
代码片段:
/* 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},
{"DAC & DDS Test" , dds_display_menu},
{"Next Steps", ns_display_menu },
{"", NULL }
};
功能展示:
扩展任务:设计一个类似信号发生器功能的例程。可在示例程序上修改。通过命令或按键,设置DAC输出波形,可通过flash存储历史波形等信息
物料清单:EK-RA6M5
设计思路:
扩展任务将和进阶任务中新增的命令、基础任务中使用DAC生成波形结合,共同实现通过串口命令行发送相应指令,开发板输出对应的波形。具体配置前面任务已经介绍过,不再介绍。
软件流程图:
代码片段:
#include <math.h>
#define M_PI 3.14159265358979323846
uint16_t dac_val = 0;
uint8_t change_flag = 0;
/**********************************************************************************************************************
* Function Name: gpt_blue_callback
* Description : Callback function for driver g_gpt_blue.
* Argument : p_args
* Return Value : .
*********************************************************************************************************************/
void gpt_blue_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
static double w = 0.0;
w += M_PI / 30;
if(w >= M_PI*2)
{
w = 0.0;
}
dac_val = (sin(w)+1) /2 * 4095;
change_flag = 1;
//R_DAC_Write(&g_dac0_ctrl, dac_val);
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];
}
}
}
test_fn dds_display_menu(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, DDS_MODULE_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, DDS_SUB_OPTIONS);
print_to_console((void*)s_print_buffer);
/* 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);
/* provide small delay so board_status should be up to date */
vTaskDelay (s_ticks_to_wait);
xEventGroupSetBits (g_update_console_event, STATUS_DISPLAY_MENU_KIS);
while (CONNECTION_ABORT_CRTL != c)
{
c = input_from_console ();
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
break;
}
if(MENU_SIN_CRTL == c)
{
while(1)
{
R_DAC_Write(&g_dac0_ctrl, dac_val);
}
}
else if(MENU_SQUARE_CRTL == c)
{
}
else if(MENU_TRIANGULAR_CRTL == c)
{
}
}
xEventGroupClearBits (g_update_console_event, STATUS_DISPLAY_MENU_KIS);
return (0);
}
功能展示:
进入DAC&DDS Test测试界面后,输入键盘上的小写字母a、b、c将分别通过DAC输出正弦波、方波、三角波。
示波器观察到的波形
心得体会
非常感谢活动主办方提供这么棒的开发板供大家学习,个人感觉这次的难度是今年四期中最难的了,但是啃硬骨头啃完的感觉真爽,未来争取不让这块开发板吃灰,板载这么多外设,争取让它继续发光发热。
三、可编译下载的代码
源工程下载
-
上传了资料:
【Follow me第二季第3期】+项目总结
-
加入了学习《【Follow me第二季第3期】+项目总结》,观看 【Follow me第二季第3期】+项目总结
-
加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门
- 2024-12-30
-
加入了学习《直播回放: DigiKey FollowMe 第二季 第4期 Arduino Nano RP2040 Connect 任务讲解》,观看 Arduino Nano RP2040 Connect 任务讲解
- 2024-11-18
-
发表了主题帖:
【X-NUCLEO-53L4A3】+驱动TOF传感器测距
本帖最后由 superw 于 2024-11-18 09:01 编辑
此贴主要介绍如何使用X-CUBE-TOF1软件包基于自定义的VL53系列TOF传感器进行驱动,并进行基础测距功能,在OLED屏幕上可实时显示。正常使用VL53系列TOF传感器肯定是自己设计的板子,一个VL53L4ED传感器二十来块,一个X-NUCLEO-53L4A3扩展板将近三百,而且扩展板上也没什么其他特殊的IC,选哪个不用多说。
硬件清单
X-NUCLEO-53L4A3、NUCLEO-F401RE、0.96寸OLED屏幕(IIC接口)
测距工程创建
使用STM32CubeIDE创建NUCLEO-F401RE工程过程略过,非常简单,网上教程也一大堆。
配置VL53L4ED需要的IIC接口和GPIO,以及OLED需要的IIC接口。
根据X-NUCLEO-53L4A3原理图,板载的VL53L4ED与STM32F401RE的PB8(SCL)、PB9(SDA)连接,并且时钟线和数据线都通过上拉电阻拉高,可以使用GPIO软件模拟IIC时序驱动,也可以使用STM32F401RE片上的硬件IIC控制器驱动。VL53L4ED的XSHUT引脚与PB3连接,通过上拉电阻拉高,用做GPIO输出;GPIO1引脚与PA4连接,通过上拉电阻拉高,用做中断输入。OLED屏幕与STM32F401RE的PA8(SCL)、PB4(SDA)连接,并且时钟线和数据线都通过上拉电阻拉高,可以使用GPIO软件模拟IIC时序驱动,也可以使用STM32F401RE片上的硬件IIC控制器驱动。
下面对于VL53L4ED和OLED屏幕,都采用硬件IIC驱动,PB8、PB9对应IIC1,PA8、PB4对应IIC3。
引脚配置如上图所示,在创建工程时,STM32CubeF4软件包默认将下载接口、调试USART接口、板载按键、板载LED配置完成,剩下需要自己配置VL53L4ED和OLED屏幕用到的IO资源。
VL53L4ED使用I2C总线进行通信,最大支持1MHz,地址为0x52。开启STM32F401RE的I2C1,模式为Fast Mode,时钟频率为400KHz。
OLED使用I2C总线进行通信,最大支持1MHz,地址为0x52。开启STM32F401RE的I2C3,模式为Fast Mode,时钟频率为400KHz。
选择Middleware and Software Packs,接着选择X-CUBE-TOF1软件包,在里面选择器件为VL53L4ED,板子为custom,应用为53L4A3_SimpleRanging,之后点击Ok即可。
重新进入X-CUBE-TOF1软件包,将软件包需要的IP与STM32F401RE配置的一一对应即可,之后生成代码。
在Drivers/BSP目录下创建OLED文件夹,将OLED相关的I2C驱动复制进去,把相关I2C操作的句柄改为I2C3。
X-CUBE-TOF1软件包默认创建了app_tof.c、app_tof.h应用层代码,可以在main函数里,直接调用MX_TOF_Init、MX_TOF_Process函数完成测距。
MX_TOF_Init通过函数调用完成USART2打印串口初始化,VL53L4ED传感器初始化。
MX_TOF_Process通过获取VL53L4ED器件ID,获取属性,配置TOF后,开启TOF传感器,并间隔POLLING_PERIOD周期获取一次测距结果并打印出来。
测距结果包括目标NumberOFTargets数量,Distance距离,Status状态,Ambient环境光,Signal信号速率。需要注意,只有当Status状态值为0时,才代表TOF传感器捕捉到目标物体,当获取到状态值不为0的信息时,可以直接舍去,无意义。
static void display_result(RANGING_SENSOR_Result_t *Result)
{
uint8_t i;
uint8_t j;
char message[50];
for (i = 0; i < RANGING_SENSOR_MAX_NB_ZONES; i++)
{
OLED_NewFrame();
sprintf(message, "Targets = %lu", (unsigned long)Result->ZoneResult[i].NumberOfTargets);
OLED_PrintString(0, 0, message, &font12x12, OLED_COLOR_NORMAL);
for (j = 0; j < Result->ZoneResult[i].NumberOfTargets; j++)
{
sprintf(message, "Status = %ld", (long)Result->ZoneResult[i].Status[j]);
OLED_PrintString(0, 20, message, &font12x12, OLED_COLOR_NORMAL);
sprintf(message, "Distance = %ld mm", (long)Result->ZoneResult[i].Distance[j]);
OLED_PrintString(0, 35, message, &font12x12, OLED_COLOR_NORMAL);
}
OLED_ShowFrame();
}
}
static void MX_VL53L4ED_SimpleRanging_Process(void)
{
uint32_t Id;
CUSTOM_RANGING_SENSOR_ReadID(CUSTOM_VL53L4ED, &Id);
CUSTOM_RANGING_SENSOR_GetCapabilities(CUSTOM_VL53L4ED, &Cap);
Profile.RangingProfile = VL53L4ED_PROFILE_CONTINUOUS;
Profile.TimingBudget = TIMING_BUDGET;
Profile.Frequency = 0; /* Induces intermeasurement period, NOT USED for normal ranging */
Profile.EnableAmbient = 1; /* Enable: 1, Disable: 0 */
Profile.EnableSignal = 1; /* Enable: 1, Disable: 0 */
/* set the profile if different from default one */
CUSTOM_RANGING_SENSOR_ConfigProfile(CUSTOM_VL53L4ED, &Profile);
status = CUSTOM_RANGING_SENSOR_Start(CUSTOM_VL53L4ED, RS_MODE_BLOCKING_CONTINUOUS);
while (1)
{
/* polling mode */
status = CUSTOM_RANGING_SENSOR_GetDistance(CUSTOM_VL53L4ED, &Result);
if (status == BSP_ERROR_NONE)
{
print_result(&Result);
display_result(&Result);
}
HAL_Delay(POLLING_PERIOD);
}
}
前面已经将OLED显示驱动复制到工程里,需要重新构建一个显示测距结果的函数display_result,与print_result一块调用,就可以在串口和OLED显示屏上分别看到测距结果。
实现效果
[localvideo]8a527328aed246189c5dd9f2f4e0005f[/localvideo]
尺子长度为30cm,视频中可以看到实际测距效果误差在mm之内
-
发表了主题帖:
【X-NUCLEO-53L4A3】+TOF传感器开箱
本帖最后由 superw 于 2024-11-18 08:50 编辑
本次评测提供的开发套件由X-NUCLEO-53L4A3和NUCLEO-F401RE组成,其中NUCLEO-F401RE属于经典的NUCLEO-64系列开发板,X-NUCLEO-53L4A3属于TOF传感器扩展板。
一、X-NUCLEO-53L4A3扩展板
X-NUCLEO-53L4A3扩展板由扩展板本身、两个盖片和三个垫片组成。其中垫片的规格分别为0.25mm,0.5mm,1mm,用来模拟不同的空气间隙。盖片用来保护传感器内部的红外传感器不受灰尘侵袭和外部因素干扰,特别是在高粉尘环境下作用明显。
X-NUCLEO-53L4A3扩展板板载一个VL53L4ED TOF传感器,其衍生型号有VL53L4CD,看数据手册了解到,VL53L4ED应该是在VL53L4CD基础上进一步扩展了其工作温度范围,支持在-40°C~ 105°C范围内工作,其余特点二者一样。
此外,X-NUCLEO-53L4A3扩展板还支持外接两个SATEL-VL53L4ED模块和一个具有IIC接口的OLED模块。
二、NUCLEO-F401RE开发板
NUCLEO-F401RE开发板采用STM32F401RE作为MCU,板载ST-LINK v2下载器,MCU所有IO在开发板上以排针和Arduino连接器的形式引出。
三、装配
将X-NUCLEO-53L4A3扩展板与NUCLEO-F401RE开发板通过各自的Arduino连接器对插,并且把0.96寸OLED显示模块接入,默认提供的盖片贴到传感器表面上,用来防止灰尘,这样就得到上面图片的形式。接入OLED模块时,需要注意自己的OLED模块和X-NUCLEO-53L4A3扩展板上的OLED接口线序是否相符,X-NUCLEO-53L4A3扩展板上的OLED接口从下到上分别为GND、VCC、SCL、SDA。
通过STM32F401RE的IIC1控制VL53L4ED,对其进行初始化,发送测距指令等,IIC3控制OLED进行显示。
四、软件需求
对于VL53L4ED传感器的驱动,ST提供了X-CUBE-TOF1的软件包,支持全系列的TOF传感器和官方扩展板。
对于NUCLEO-F401RE开发板,打算使用STM32CubeIDE进行开发,具有代码编辑、编译、链接、调试、图形化配置等多种功能。
开发时需要注意VL53L4ED的IIC地址唯一且不可更改,为0x52。
五、相关资料汇总
X-NUCLEO-53L4A3扩展板介绍,包含原理图、用户手册、规格书等内容。
NUCLEO-F401RE开发板介绍,包含原理图、用户手册、规格书等内容。
X-CUBE-TOF1软件包下载地址https://www.st.com/en/ecosystems/x-cube-tof1.html?ecmp=tt9470_gl_link_feb2019&rt=um&id=UM3222#overview
STM32CubeIDE下载地址https://www.st.com.cn/content/st_com/zh/stm32cubeide.html
- 2024-11-14
-
加入了学习《PX4固件二次开发课程》,观看 飞控软硬体系介绍1
- 2024-10-08
-
发表了主题帖:
【Follow me第二季第2期】+项目总结
本帖最后由 superw 于 2024-11-6 02:00 编辑
文章中各任务均由图片中物料实现
物料清单:
厂商
厂商料号
名称
得捷链接
Arduino
ABX00087
Arduino UNO R4 WiFi
https://www.digikey.cn/zh/products/detail/arduino/ABX00087/20371539?s=N4IgTCBcDaIIICEAaAGNAOA7AApAXQF8g
Adafruit
4991
Adafruit I2C STEMMA QT
Rotary Encoder Breakout with
Neopixel
https://www.digikey.cn/zh/products/detail/adafruit-industries-llc/4991/14302511
Adafruit
5591
Adafruit LTR-329 Light Sensor
https://www.digikey.cn/zh/products/detail/adafruit-industries-llc/5591/16733167
一、入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!
Arduino官方的板子,当然要用Arduino IDE来开发,在Arduino官网根据自己的操作系统下载对应的安装程序,一路next即可。当然电脑硬盘不够的也可以选择使用便携版IDE或者浏览器开发。
安装完成后,搭建环境。按照官方指南https://docs.arduino.cc/tutorials/uno-r4-wifi/r4-wifi-getting-started/,安装Board Package后,即可进行编译下载代码。
硬件分析:
板载DL4通过MOS管驱动,当P102输出高电平点亮DL4,当P102输出低电平熄灭DL4。
软件实现:
通过在loop函数中循环翻转P102电平,并附加相应延时,即可实现板载LED闪烁。Arduino官方开发板默认串口使用Serial.begin进行初始化,其他通信串口使用Serialx.begin初始化(x=1,2,...),串口初始化成功后,可通过print,println函数进行打印。
代码实现:
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println("Hello EEWorld!");
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
上面代码实现任务一,控制板载LED Blink,同时通过串口打印Hello EEWorld!
实现效果:
二、基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线
对于Arduino UNO R4 WiFi板上资源,官方提供了详细的指南。
对于点阵LED,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/
对于片上DAC,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/dac/
对于片上OPAMP,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/opamp/
对于片上ADC,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/adc-resolution/
综合根据上面指南,可以完成任务二。
硬件分析:本任务涉及到的硬件资源,开发板上均已具备,且对于DAC、OPAMP、ADC均为MCU片上资源,直接通过对应函数操作硬件即可。
对于OPAMP放大DAC信号,需要使用面包板,通过片上OPAMP运放单元搭建一个简易的运算放大器。原理图如下:
上面的实现是一个电压跟随器,但电压跟随器可以理解为一个特殊的运算放大器,增益为1,输出电压与输入电压同幅值,同频率。电压跟随器在电路中经常起到缓冲和隔离的作用。
将A0(DAC输出)接入A1(OPAMP的正向输入端),A2(OPAMP的反向输入端)和A3短接(OPAMP的输出端),此时便可在A3用示波器观察到与A0的输出波形一致。将A3(OPAMP的输出端)与A4(ADC采集)短接,便可以获取到ADC采集到的信号电压。
代码实现:
#include "analogWave.h"
#include "OPAMP.h"
#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"
analogWave wave(DAC);
ArduinoLEDMatrix matrix;
int freq = 10;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
wave.sine(freq);
OPAMP.begin(OPAMP_SPEED_HIGHSPEED);
analogReadResolution(14);
matrix.begin();
}
void loop() {
// put your main code here, to run repeatedly:
int adc_value = analogRead(A4);
Serial.println("current adc value is " + String(adc_value));
// Make it scroll!
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
matrix.textScrollSpeed(50);
// add the text
const char text[] = " EEWorld & DigiKey ";
matrix.textFont(Font_4x6);
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(text);
matrix.endText(SCROLL_LEFT);
matrix.endDraw();
}
实现效果:
对于12x8点阵LED,上电后会看到EEWorld & DigiKey的字样。
手机相机拍的不是很清,人眼视觉效果会相对好点。
用DAC生成正弦波,并经过OPAMP放大DAC信号,现象可通过示波器观察得到。
由于手里没有插件电阻,将片上的OPAMP运放配置为电压跟随器模式,即输入为什么信号,输出就是什么信号。图中黄色为原始DAC正弦波信号,绿色为经过电压跟随器后输出的正弦波信号,二者幅值频率均相等。
用ADC采集生成的正弦波信号,并打印到串口,借助Arduino IDE的串口上位机可将波形大致呈现出来。
三、进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)
四、扩展任务一:通过外部LTR-329 环境光传感器,上传光照度到HA,通过HA面板显示数据
首先,非常感谢这期老师给大家带来的直播任务讲解,里面清楚的介绍了如何在windows平台上搭建HomeAssistant环境。当然,我自己也尝试了在泰山派上通过docker安装HomeAssistant,但貌似是由于Docker Hub不稳定的问题,总是访问超时,在ubuntu上也不会魔法上网,只能放弃。后来又尝试安装CasaOS(系统本身已具有docker),但最终还是需要通过docker访问HomeAssistant的Docker Hub,也未能完整实现。最终选择了直播中老师讲解的方法,在windows上安装Docker Desktop,之后拉取HomeAssistant镜像,并创建容器。
具体搭建过程可参考直播讲解https://training.eeworld.com.cn/video/40793
HomeAssistant界面
EMQX(MQTT)服务器界面
硬件分析:
上图为任务推荐的LTR329光照传感器模块,板载Qwiic接口。对应Adafruit采购型号为5591。
上图为任务之外选购的编码器模块,板载Qwiic接口。对应Adafruit采购型号为4991。板载一个内置固件的MCU,采集板载的编码器信息,并且支持外部IIC总线访问来获取。
上图为Arduino开发板的J2接口,硬件形式为Qwiic,可通过SH1.0排线将上述两个模块串联到一块,共同挂载在IIC总线上,通过IIC主机寻址不同的设备地址,访问设备。
注意:LTR329的IIC地址为0x29,编码器模块的IIC地址为0x36。
软件实现:
打开Library Manager,搜索框输入LTR329,安装对应的Library。详细介绍及驱动代码可见https://learn.adafruit.com/adafruit-ltr-329-ltr-303/overview
本次活动选择的另一个器件是I2C QT ROTARY ENCODER,片上集成了一个编码器和一个WS2812,单片机下载seesaw固件,通过I2C接口获取命令,进一步通过单片机读取编码器的值,控制WS2812。搜索框输入seesaw,安装对应的Library。详细介绍及驱动代码可见https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder
当然,要通过WiFi接入HA平台,最重要的还需要一个home-assistant-integration库,通过这个库,可以创建各种设备类型,包括灯、按键、传感器、锁等等。具体库介绍可参考https://dawidchyrzynski.github.io/arduino-home-assistant/index.html
环境搭建和各种library安装好之后,就可以进行代码编写。
代码实现:
#include "WiFiS3.h"
#include "arduino_secrets.h"
#include "Adafruit_LTR329_LTR303.h"
#include <ArduinoHA.h>
#include <seesaw_neopixel.h>
#define MQTT_OBJECT_ID "followme"
#define BROKER_ADDR IPAddress(192,168,10,246)
#define MQTT_PORT 1883
#define MQTT_USERNAME "admin"
#define MQTT_PASSWORD "123"
#define SS_NEOPIX 6
#define SEESAW_ADDR 0x36
seesaw_NeoPixel sspixel = seesaw_NeoPixel(1, SS_NEOPIX, NEO_GRB + NEO_KHZ800, &Wire1);
Adafruit_LTR329 ltr = Adafruit_LTR329();
WiFiClient client;
HADevice device(MQTT_OBJECT_ID);
HAMqtt mqtt(client, device);
HAButton buttonR("myButtonR");
HAButton buttonG("myButtonG");
HAButton buttonB("myButtonB");
HASensorNumber lightSensor("mylight");
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;
unsigned long lastUpdateAt = 0;
void onButtonCommand(HAButton* sender)
{
if (sender == &buttonR) {
// button A was clicked, do your logic here
sspixel.setPixelColor(0, sspixel.Color(255, 0, 0));
} else if (sender == &buttonG) {
// button B was clicked, do your logic here
sspixel.setPixelColor(0, sspixel.Color(0, 255, 0));
} else if (sender == &buttonB) {
sspixel.setPixelColor(0, sspixel.Color(0, 0, 255));
}
sspixel.show();
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
wifi_init();
ltr329_init();
pixel_init();
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
buttonR.setIcon("mdi:fire");
buttonR.setName("RED");
buttonG.setIcon("mdi:home");
buttonG.setName("GREEN");
buttonB.setIcon("mdi:water");
buttonB.setName("BLUE");
buttonR.onCommand(onButtonCommand);
buttonG.onCommand(onButtonCommand);
buttonB.onCommand(onButtonCommand);
lightSensor.setIcon("mdi:home");
lightSensor.setName("light value");
mqtt.begin(BROKER_ADDR, MQTT_PORT, MQTT_USERNAME, MQTT_PASSWORD);
}
void loop() {
// put your main code here, to run repeatedly:
bool valid;
uint16_t visible_plus_ir, infrared;
mqtt.loop();
if ((millis() - lastUpdateAt) > 1000)
{ // 1000ms debounce time
if (ltr.newDataAvailable())
{
valid = ltr.readBothChannels(visible_plus_ir, infrared);
if (valid)
{
Serial.print("CH0 Visible + IR: ");
Serial.print(visible_plus_ir);
Serial.print("\t\tCH1 Infrared: ");
Serial.println(infrared);
lightSensor.setValue(visible_plus_ir);
}
}
lastUpdateAt = millis();
}
}
void wifi_init()
{
// 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 SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
printWifiStatus();
}
void ltr329_init()
{
Serial.println("Adafruit LTR-329 advanced test");
if ( ! ltr.begin(&Wire1) ) {
Serial.println("Couldn't find LTR sensor!");
while (1) delay(10);
}
Serial.println("Found LTR sensor!");
ltr.setGain(LTR3XX_GAIN_2);
ltr.setIntegrationTime(LTR3XX_INTEGTIME_100);
ltr.setMeasurementRate(LTR3XX_MEASRATE_200);
}
void pixel_init()
{
Serial.println("Looking for seesaw!");
if (!sspixel.begin(SEESAW_ADDR)) {
Serial.println("ERROR! seesaw not found");
while(1) delay(1);
}
Serial.println("seesaw started");
// set not so bright!
sspixel.setBrightness(20);
sspixel.show();
}
/* -------------------------------------------------------------------------- */
void printWifiStatus() {
/* -------------------------------------------------------------------------- */
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your board's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}
上面代码实现了两个功能,一个是将LTR-329获取到的光照强度上传到HA平台,另一个是通过HA平台创建三个按键,分别用来控制WS2812的红、绿、蓝三种颜色。
硬件连接如图
实现效果
在HA平台上可看见上传的光照强度值,具体现象可见视频。
五、心得体会
非常感谢官方提供的Arduino板卡,这块板子22年发布的,在技术创新和功能上都非常有特色,最感兴趣的还是上面12x8点阵LED了,可以生成表情、动物、字符等信息。另外,这期活动还跟着教程学会了home assistant的搭建和使用,后面也要继续尝试在泰山派上成功搭建起来。最后希望follow me活动越办越好,给大家带来更多更优秀的大厂开发板。
六、可编译下载的代码
-
加入了学习《【Follow me第二季第2期】+视频介绍》,观看 【Follow me第二季第2期】
-
加入了学习《FollowMe 第二季:2 - Arduino UNO R4 Wi-Fi 及任务讲解》,观看 Arduino UNO R4 Wi-Fi 及任务讲解
- 2024-09-12
-
加入了学习《Digi-Key: Follow Me 系列(2) 直播回放》,观看 Adafruit ESP32-S3 TFT Feather开发板使用入门