- 2024-12-10
-
加入了学习《【Follow me第二季第3期】任务汇总》,观看 【Follow me第二季第3期】任务汇总
-
加入了学习《【Follow me第二季第3期】所有任务汇总》,观看 【Follow me第二季第3期】所有任务汇总
-
上传了资料:
【Follow me第二季第3期】任务4
-
上传了资料:
【Follow me第二季第3期】任务2、3
-
上传了资料:
【 Follow me第二季第3期】任务1
- 2024-12-08
-
加入了学习《【Follow me第二季第3期】LED程序修改及编译,Blink及按键测试》,观看 【Follow me第二季第3期】LED程序修改及编译,Blink及按键测试
-
加入了学习《【Follow me第二季第4期】任务汇报》,观看 【Follow me第二季第4期】任务汇报
- 2024-12-05
-
发表了主题帖:
【Follow me第二季第3期】EK_RA6M5任务汇总
本帖最后由 wiwhh 于 2024-12-10 16:05 编辑
【Follow me第二季第3期】EK_RA6M5任务汇总
0 开箱环节
第一次收到国外的快递非常的兴奋,感谢得捷和EEWorld两位父亲的支持。包装非常高级环保,是用一个没见过的纸做箱子的填充,包装用料非常足哈。接下来说说板子,首先板子非常的大哈,给的线材也非常多,还贴心的给了一个USB转接线。板子用料非常足啊,不仅有以太网模块、两块不同接口的flash,最重要的还板载了Jlink,这大大减少了桌面上的一堆线,调试起来非常的方便哈。
1 入门任务:搭建环境,下载调试示例程序,Blink,按键
1.1搭建环境
下载e2 studio/RASC
获取e2 studio 的安装包,项目地址为:https://github.com/renesas/fsp/releases
如果使用e2 studio开发,仅仅需要下载setup_fsp_v5_5_0_e2s_v2024-07.exe文件即可,如果习惯使用keil或者其他工具开发,就需要下载另外两个文件。例如官方给出的例程是e2 studio工程,我们下载e2 studio安装包即可。
安装e2 studio
双击打开setup_fsp_v5_5_0_e2s_v2024-07.exe
更改安装目录,不能含有空格
直接统统下一步等待安装完成,同时设置桌面快捷方式
1.2 Blink,按键
新建keil工程
打开rasc,填写工程名称和工程路径。
为了能够快速开发,我们选则该板子的工程模板。
3.选择FreeRTOS工程,方便后续开发。
4.工程创建完成后,检查是否有下载算法,如果没有,将工程切换为其他芯片,再切换回来就会有下载算法。
LED闪烁
/* Blinky Thread entry function */
void blinky_thread_entry (void * pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
/* LED type structure */
bsp_leds_t leds = g_bsp_leds;
/* If this board has no LEDs then trap here */
if (0 == leds.led_count)
{
while (1)
{
; // There are no LEDs on this board
}
}
/* Holds level to set for pins */
bsp_io_level_t pin_level = BSP_IO_LEVEL_LOW;
while (1)
{
/* Enable access to the PFS registers. If using r_ioport module then register protection is automatically
* handled. This code uses BSP IO functions to show how it is used.
*/
R_BSP_PinAccessEnable();
/* Update all board LEDs */
for (uint32_t i = 0; i < leds.led_count; i++)
{
/* Get pin to toggle */
uint32_t pin = leds.p_leds[i];
/* Write to this pin */
R_BSP_PinWrite((bsp_io_port_pin_t) pin, pin_level);
}
/* Protect PFS registers */
R_BSP_PinAccessDisable();
/* Toggle level for next write */
if (BSP_IO_LEVEL_LOW == pin_level)
{
pin_level = BSP_IO_LEVEL_HIGH;
}
else
{
pin_level = BSP_IO_LEVEL_LOW;
}
vTaskDelay(configTICK_RATE_HZ);
}
}
以下是这段代码的主要逻辑:
初始化:函数首先检查板子上是否有LED,如果leds.led_count为0(即没有LED),则进入一个无限循环,什么也不做。
设置引脚电平:定义了一个变量pin_level来保存要设置给LED引脚的电平状态,初始值为低电平BSP_IO_LEVEL_LOW。
主循环:
在每次循环开始时,调用R_BSP_PinAccessEnable()启用对PFS(Pin Function Select)寄存器的访问,这通常是必要的,以确保能够修改引脚的功能。
接着通过一个for循环遍历所有LED,并使用R_BSP_PinWrite()函数将当前的pin_level写入到每个LED对应的引脚上,从而控制LED的状态(亮或灭)。
循环结束后,调用R_BSP_PinAccessDisable()禁用对PFS寄存器的访问,以保护这些配置不被意外改变。
最后,根据当前的pin_level值进行取反操作,以便在下一次循环中切换LED的状态。
使用vTaskDelay(configTICK_RATE_HZ)让线程延时一段时间,这样LED就会以一定的频率闪烁。configTICK_RATE_HZ定义了延时的时间长度,通常它表示系统的一个tick周期。
整个过程不断地重复,导致LED以固定的间隔时间(由vTaskDelay中的参数决定)在亮与灭之间切换,从而实现了LED闪烁的效果。
按键
按键功能我们通过中断实现,打开rasc配置工程
首先就是配置引脚功能
接着在stacks中添加对应的stack,也就是外部中断9和10
配置他们的属性,包括中断名称,触发方式,中断优先级和回调函数
我们新建一个Key_driver.c文件来实现按键的功能。主要就两个对外的接口,一个就是中断的初始化函数。另外一个就是在中断函数中注册回调函数。这样就能实现按键功能的解耦。下面就是该文件的代码。
#include "driver_key.h"
/* KEY 外部中断初始化函数 */
void Key_IRQ_Init(void)
{
fsp_err_t err = FSP_SUCCESS;
/* Open ICU module */
err = R_ICU_ExternalIrqOpen(&g_external_irq9_ctrl, &g_external_irq9_cfg);
err = R_ICU_ExternalIrqOpen(&g_external_irq10_ctrl, &g_external_irq10_cfg);
/* 允许中断 */
err = R_ICU_ExternalIrqEnable(&g_external_irq9_ctrl);
err = R_ICU_ExternalIrqEnable(&g_external_irq10_ctrl);
}
// 注册回调函数的接口
void key_register_callbacks(callback_func_t key0_cb, callback_func_t key1_cb) {
callbacks.key0_cb = key0_cb;
callbacks.key1_cb = key1_cb;
}
// key0 的中断处理函数
void key0_callback(external_irq_callback_args_t *p_args) {
if (callbacks.key0_cb != NULL) {
callbacks.key0_cb();
}
}
// key1 的中断处理函数
void key1_callback(external_irq_callback_args_t *p_args) {
if (callbacks.key1_cb != NULL) {
callbacks.key1_cb();
}
}
在LED线程中调用按键的接口,注册回调函数来调节LED的闪烁频率。
static uint8_t Led_frequencyindex = 0;
static uint32_t Led_frequency[] = {10, 300, 500, 1000, 1300};
void my_key0_handler(void) {
Led_frequencyindex = (Led_frequencyindex+1)%5;
}
void my_key1_handler(void) {
if(Led_frequencyindex == 0)
{
Led_frequencyindex = sizeof(Led_frequency)/sizeof(Led_frequency[0]) - 1;
}
else
{
Led_frequencyindex = (Led_frequencyindex-1)%(sizeof(Led_frequency)/sizeof(Led_frequency[0]));
}
}
void blinky_thread_entry (void * pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
/* LED type structure */
bsp_leds_t leds = g_bsp_leds;
Key_IRQ_Init();
key_register_callbacks(my_key0_handler, my_key1_handler);
/* If this board has no LEDs then trap here */
if (0 == leds.led_count)
{
while (1)
{
; // There are no LEDs on this board
}
}
/* Holds level to set for pins */
bsp_io_level_t pin_level = BSP_IO_LEVEL_LOW;
while (1)
{
/* Enable access to the PFS registers. If using r_ioport module then register protection is automatically
* handled. This code uses BSP IO functions to show how it is used.
*/
R_BSP_PinAccessEnable();
/* Update all board LEDs */
for (uint32_t i = 0; i < leds.led_count; i++)
{
/* Get pin to toggle */
uint32_t pin = leds.p_leds[i];
/* Write to this pin */
R_BSP_PinWrite((bsp_io_port_pin_t) pin, pin_level);
}
/* Protect PFS registers */
R_BSP_PinAccessDisable();
/* Toggle level for next write */
if (BSP_IO_LEVEL_LOW == pin_level)
{
pin_level = BSP_IO_LEVEL_HIGH;
}
else
{
pin_level = BSP_IO_LEVEL_LOW;
}
vTaskDelay(Led_frequency[Led_frequencyindex]);
}
}
全局变量定义:定义了两个全局变量Led_frequencyindex和Led_frequency。Led_frequencyindex用于记录当前选择的闪烁频率索引,而Led_frequency数组存储了五个不同的闪烁频率(单位为毫秒),分别为10ms、300ms、500ms、1000ms和1300ms。
按键处理函数:
my_key0_handler:当按下键0时,Led_frequencyindex递增1,然后取模5,确保其值始终在0到4之间循环,对应于Led_frequency数组中的五个频率。
my_key1_handler:当按下键1时,如果Led_frequencyindex已经是0,则将其设置为最大索引值(即4),否则减1并取模5,同样保证索引值在有效范围内。
线程入口函数blinky_thread_entry
初始化按键中断和注册按键回调函数my_key0_handler和my_key1_handler,这样当用户按下键0或键1时,会分别调用这两个函数来更新Led_frequencyindex。
1.3 实现效果
2. 基础任务:qspi flash和ospi flash读写速度测试及DAC波形设置
2.1 QSPI和OSPI配置
首先就是通过原理图确认QSPI使用的引脚。从图中可以看出主要就四根数据线、一根时钟线和一个片选信号。OSPI使用了8根数据线。
接下来打开配置界面,确认pin脚配置是否正确
2.2 QSPI和OSPI读写测试
QSPI
QSPI的测试代码我们可以从官方的quickstart例程中阅读,分别是qspi_write_test()和qspi_read_test()函数,我们下面分别分析一下这两个函数。
static uint32_t qspi_write_test(uint32_t block_size)
{
fsp_err_t fsp_err;
uint32_t qspi_write_result = 0;
timer_status_t status = {};
fsp_err_t err = FSP_SUCCESS;
spi_flash_protocol_t current_spi_mode;
/* Convert from kB */
block_size *= 1024;
/* The comms mode is EXTENDED_SPI by default */
current_spi_mode = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init();
if (FSP_SUCCESS == err)
{
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
uint32_t page_write_count = 0;
uint8_t * p_mem_addr;
/* Cast to req type */
p_mem_addr = (uint8_t *)QSPI_DEVICE_START_ADDRESS;
while (((page_write_count * SECTOR_SIZE) < block_size)
&& ( FSP_SUCCESS == err ) )
{
/* Erase Flash for one sector */
err = R_QSPI_Erase(&g_qspi_ctrl, p_mem_addr, SECTOR_SIZE);
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "R_QSPI_Erase Failed\r\n");
}
else
{
err = get_flash_status();
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "Failed to get status for QSPI operation\r\n");
}
/* Verify the erased block data */
uint32_t count;
for (count = 0; count < SECTOR_SIZE; count++ )
{
if (DEFAULT_MEM_VAL != p_mem_addr[count])
{
/* Verification failed, perhaps the ERASE failed */
err = FSP_ERR_NOT_ERASED;
}
}
}
p_mem_addr += SECTOR_SIZE;
page_write_count++;
}
/* Start the test timer */
fsp_err = R_GPT_Start(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
/* Cast to req type */
p_mem_addr = (uint8_t *)QSPI_DEVICE_START_ADDRESS;
page_write_count = 0;
while (((page_write_count * PAGE_WRITE_SIZE) < block_size)
&& (FSP_SUCCESS == err))
{
if (FSP_SUCCESS == err)
{
/* Write data to QSPI Flash */
/* Each block begins one character shifted along the source text. To avoid regular striping in memory */
err = R_QSPI_Write(&g_qspi_ctrl, &(sp_source[page_write_count]), p_mem_addr, PAGE_WRITE_SIZE);
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "R_QSPI_Write Failed\r\n");
}
else
{
err = get_flash_status();
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "Failed to get status for QSPI operation\r\n");
}
}
}
p_mem_addr += PAGE_WRITE_SIZE;
page_write_count++;
}
/* close QSPI module */
deinit_qspi(current_spi_mode);
fsp_err = R_GPT_Stop(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_StatusGet(g_memory_performance.p_ctrl, &status);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_Reset(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
qspi_write_result = status.counter;
return (qspi_write_result);
}
首先调用err = qpi_init(); 该函数就是调用R_QSPI_Open函数对外设进行初始化。还进行了一系列的读写测试。
接下来就是调用R_QSPI_Erase对flash进行擦除,然后读取spi的状态等待擦除完毕。
接着就是启动定时器,然后开始写入数据,等待写入完成后,停止定时器,并读取定时器的计数,以获取时间。
qspi_read_test()和qspi_write_test()函数类似,这里不做过多分析。
OSPI
OSPI调用ospi_performance_test()函数,返回读写的时间。下面分析一下这段代码
void ospi_performance_test(uint32_t data_size,
uint32_t *ospi_performance_write_result,
uint32_t *ospi_performance_read_result)
{
fsp_err_t err;
uint32_t i = 1;
if (R_CGC_Open (g_cgc.p_ctrl, g_cgc.p_cfg) != FSP_SUCCESS)
{
__asm("bkpt");
}
while (i)
{
err = R_OSPI_Open(g_ospi.p_ctrl, g_ospi.p_cfg);
if (FSP_SUCCESS != err)
{
__asm("bkpt");
}
#if HIGH_SPEED_MODE
configure_dopi_ospi();
ospi_test_wait_until_wip();
#endif
*ospi_performance_write_result = write_dopi_ospi(data_size);
ospi_test_wait_until_wip();
*ospi_performance_read_result = read_dopi_ospi(data_size);
ospi_test_wait_until_wip();
erase_dopi_ospi();
ospi_test_wait_until_wip();
#if HIGH_SPEED_MODE
configure_spi_ospi();
ospi_test_wait_until_wip();
#endif
err = R_OSPI_Close(g_ospi.p_ctrl);
if (FSP_SUCCESS != err)
{
__asm("bkpt");
}
i--;
}
}
首先打开时钟门控控制R_CGC_Open,因为后面高速模式需要配置时钟,
打开OSPI控制器。R_OSPI_Open
如果定义了HIGH_SPEED_MODE,就会配置高速模式。
write_dopi_ospi和read_dopi_ospi分别对读写速度进行了测试。
2.3 读写测试结果
电脑通过USB连接开发板
打开xshell,通过串口连接。
通过命令4开始执行上面的代码,结果打印在控制台。可以看到OSPI的通信速率要比QSPI快很多。 我尝试了将HIGH_SPEED_MODE定义为0,结果程序卡死了。
2.4 DAC测试
添加DAC设备,设置通道0,同时设置引脚。
因为没有示波器,我们也添加一个ADC设备
通过杜邦线将两个引脚连接起来
编写测试代码
/**初始化DAC*/
fsp_err_t common_init(void)
{
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err = R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
fsp_err = R_DAC_Start(&g_dac0_ctrl);
fsp_err = adc_initialize();
....
}
/**初始化ADC*/
static fsp_err_t adc_initialize(void)
{
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err = R_ADC_Open (&g_adc_ctrl, &g_adc_cfg);
if (FSP_SUCCESS != fsp_err)
{
return fsp_err;
}
fsp_err = R_ADC_ScanCfg (&g_adc_ctrl, &g_adc_channel_cfg);
if (FSP_SUCCESS != fsp_err)
{
return fsp_err;
}
fsp_err = R_ADC_ScanStart (&g_adc_ctrl);
if (FSP_SUCCESS != fsp_err)
{
return fsp_err;
}
/* Read TSN cal data (value written at manufacture, does not change at runtime) */
//fsp_err = R_ADC_InfoGet (&g_adc_ctrl, &g_adc_info_rtn);
return fsp_err;
}
uint16_t Read_ADC_Voltage_Value(void)
{
uint16_t adc_data;
(void)R_ADC_ScanStart(&g_adc_ctrl);
while (!scan_complete_flag) //等待转换完成标志
{
;
}
scan_complete_flag = false; //重新清除标志位
/* 读取通道0数据 */
R_ADC_Read(&g_adc_ctrl, ADC_CHANNEL_0, &adc_data);
return adc_data;
}
void gpt_blue_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
double angle = 2 * M_PI * i / TABLE_SIZE;
double sine_value = sin(angle);
int mapped_value = (sine_value + 1.0) * 0.5 * MAX_VALUE;
R_DAC_Write(&g_dac0_ctrl, mapped_value);
i = (++i)%TABLE_SIZE;
SEGGER_RTT_printf(0, "adcvalue :%d\r\n",Read_ADC_Voltage_Value());
.....
}
打印结果显示
3 进阶任务:示例程序中新增命令打印信息
3.1 例程中命令实现原理
在示例程序中,定义了一个名为 menu_fn_tbl 的命令结构体,用于封装每个命令的名称及其对应的功能实现。此结构体包含两个成员:一个是命令名称的字符串指针 p_name,另一个是指向函数的指针 p_func,该函数负责执行与命令相关的操作。
同时,示例程序中还定义了一个名为 s_menu_items[] 的结构体变量数组。每一个数组元素都是一个 menu_fn_tbl 类型的实例,代表菜单中的一个条目。要添加新的命令到菜单中,只需在这个数组中加入相应的命令名称和关联的功能实现即可。
当用户在控制台选择特定命令时,程序会查找 s_menu_items[] 数组,找到匹配项后调用其 p_func 成员指向的函数来执行相应功能。
typedef struct menu_fn_tbl
{
char_t * p_name; /*<! Name of Test */
test_fn ( * p_func)(void); /*<! Pointer to Test Function */
} st_menu_fn_tbl_t;
/* 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},
{"Next Steps", ns_display_menu },
{"", NULL }
};
3.2 具体命令功能实现
本命令的目标是在控制台窗口中显示通过ADC采集到的数据。在之前的小任务中,我们已经完成了ADC功能的实现,因此在这个任务中,我们将直接读取ADC的值,并将其输出到控制台上。为了达到这个目的,定义了一个名为 showADCdata 的函数,它是一个测试函数 (test_fn),负责执行以下操作:
清屏并将光标移至屏幕起始位置。
在一个无限循环中:
将当前ADC值格式化为字符串,并发送到控制台进行显示。
暂停500毫秒,以防止更新速度过快导致显示混乱。
再次清屏并重置光标位置,确保每次更新时数据都在相同的位置显示。
test_fn showADCdata(void)
{
int8_t c = -1;
uint16_t temp[50];
uint8_t i;
uint16_t adcvalue;
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);
while(1)
{
sprintf (s_print_buffer, "%s:%d\r\n", "ADC:", g_adcvalue);
print_to_console((void*)s_print_buffer);
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
vTaskDelay(500);
print_to_console((void*)s_print_buffer);
}
return (0);
}
3.3 功能展示
我们将ADC引脚连接至3.3V,可以看到控制台正确输出了adc的值。
4 扩展任务:信号发生器
4.1 功能流程
简单的功能流程图如下图所示,详细的功能介绍会在代码部分详解。
4.2 工程配置
为了方便功能的实现,我有连接了一个额外的按键模块
通过RASC配置初始工程,用到的模块如图左边所示。
4.3 代码部分
main 函数
void hal_entry(void)
{
/* TODO: add your own code here */
uint16_t i = 0;
uint16_t j = 0;
uint32_t FlashID = 0;
uint32_t FlashDeviceID = 0;
uint16_t u16temp;
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
printf("hellow world\r\n");
QSPI_Flash_Init(); // 串行FLASH初始化
adc_init();
dac_init();
R_ICU_ExternalIrqOpen(&g_external_irq9_ctrl, &g_external_irq9_cfg);
R_ICU_ExternalIrqOpen(&g_external_irq10_ctrl, &g_external_irq10_cfg);
/* 允许中断 */
R_ICU_ExternalIrqEnable(&g_external_irq9_ctrl);
R_ICU_ExternalIrqEnable(&g_external_irq10_ctrl);
while(1)
{
u16temp = getadcvalue();
writebuf[i++] = u16temp;
printf("line1=%d\r\n",u16temp);
if(i == 1000)
{
i = 0;
QSPI_Flash_Write((uint8_t *)writebuf, 0x000000, 2000);
}
if(readflag == 1)
{
readflag = 0;
QSPI_Flash_BufferRead((uint8_t *)readbuf,0x000000,2000);
for( j = 0; j<1000; j++)
{
printf("line2=%d\r\n",readbuf[j]);
}
}
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_MICROSECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
// key0 的中断处理函数
void key0callback(external_irq_callback_args_t *p_args) {
if(get_wave_type() == WAVE_SINE)
{
set_wave_type(WAVE_SQUARE);
}
else
{
set_wave_type(WAVE_SINE);
}
}
// key1 的中断处理函数
void key1callback(external_irq_callback_args_t *p_args) {
readflag = 1;
}
uint8_t amplitude_index;
// key2 的中断处理函数
void key2callback(external_irq_callback_args_t *p_args) {
set_wave_amplitude(amplitude_index++);
amplitude_index = amplitude_index%4;
}
uint32_t g_frequency = 500;
// key3 的中断处理函数
void key3callback(external_irq_callback_args_t *p_args) {
set_wave_frequency(g_frequency);
g_frequency+=200;
g_frequency = g_frequency%1500;
}
该工程没有使用操作系统,只有主函数一个线程,这部分代码主要实现以下功能:
初始化外设,包含以下这些外设:
初始化uart4,用于输出调式信息和用于输出ADC波形。
初始化QSPI,主要用于存储历史波形,存储的数据为adc采集到的波形。
初始化DAC,用于输出0-3.3v的自定义波形,设置的波形目前只有正弦波和方波。
初始化ADC,由于手头没有示波器工具,只能使用板载的ADC,由于测试使用ADC的功能在主函数中实现,采样率只有勉强1khz。
开启外部中断,实现按键功能,用于调整波形和输出历史波形。
开启一个1ms执行一次的循环,实现的功能如下:
获取ADC采样的数据,adc的值存储在一个全局变量中。
将ADC的值存储到一个数组中,当数组存了大概1s的数据后,存储到外部flash中,该过程可能比较耗时,因为擦除这个过程花费很多时间,会稍微影响其他任务,但不影响DAC的波形输出。
如果检测到读取历史波形的按键按下,会读取flash中的数据,并打印出来。
定时器中断
DAC的功能主要依赖定时器中断,下面列出这部分代码
volatile uint32_t timer_ticks = 0; // 用于计数定时器中断次数
uint32_t wave_frequency = 1000; // 波形频率,默认1kHz
uint64_t pclkd_freq_hz //时钟频率
uint16_t wave_amplitude = 2048; // DAC最大值
WaveType current_wave_type = WAVE_SINE; // 默认为正弦波
uint16_t dac_value = 0;
// 定时器中断回调函数
void dac_timercallback(timer_callback_args_t * p_args) {
FSP_PARAMETER_NOT_USED(p_args);
switch (current_wave_type) {
case WAVE_SINE:
// 正弦波的计算
dac_value = (wave_amplitude * (1 + sin(2 * M_PI * timer_ticks / wave_frequency))) / 2;
break;
case WAVE_SQUARE:
// 方波的计算
dac_value = (timer_ticks < wave_frequency / 2) ? wave_amplitude : 0;
break;
}
R_DAC_Write(&g_dac0_ctrl,dac_value);
timer_ticks++;
if (timer_ticks >= wave_frequency) {
timer_ticks = 0;
}
}
// 设置波形频率
void set_wave_frequency(uint32_t freq) {
// 重新配置定时器以适应新的频率
uint32_t period_counts =(uint32_t) (((uint64_t) pclkd_freq_hz ) / freq)-1;
// 这里需要根据实际使用的定时器进行相应的配置
R_GPT_PeriodSet(&dac_timer_ctrl, period_counts);
}
// 设置波形幅值
void set_wave_amplitude(Amplitude amp) {
switch(amp)
{
case AMP_500:
wave_amplitude = 500;
break;
case AMP_1500:
wave_amplitude = 1500;
break;
case AMP_2500:
wave_amplitude = 2500;
break;
case AMP_3500:
wave_amplitude = 3500;
break;
}
}
// 设置波形类型
void set_wave_type(WaveType type) {
current_wave_type = type;
}
WaveType get_wave_type(void) {
return current_wave_type;
}
在dac_timercallback定时器中断回调函数中实现的功能如下。
根据用户选择的波形计算DAC输出值
正弦波
使用 sin() 函数生成周期性的波动。
将时间(timer_ticks)映射到正弦波的一个完整周期。
调整输出范围以匹配DAC的输出值,确保输出在0到最大幅值之间。
方波
根据定时器计数值 (timer_ticks) 判断当前是否处于半个周期内。
如果是前半个周期,则输出最大幅值; 否则输出0,形成高、低电平的交替。
同时,我们还提供了以下的几个外部接口,用以调整波形
set_wave_frequency:设置波形,主要利用R_GPT_PeriodSet接口来设置定时器的周期,通过R_FSP_SystemClockHzGet接口可以获取定时器的时钟频率,利用时钟频率和需求的定时器频率就可以计算出定时器的溢出值。
set_wave_amplitude:设置幅值,这部分简单,调整wave_amplitude变量就能实现DAC波形的变化。
set_wave_type:设置波形的类型,即用以输出正弦波和方波。
adc中断
uint16_t getadcvalue(void)
{
return g_adcvalue;
}
void g_adc0_callback(adc_callback_args_t * p_args)
{
FSP_PARAMETER_NOT_USED(p_args);
R_ADC_Read(&g_adc0_ctrl, ADC_CHANNEL_0, &g_adcvalue);
(void)R_ADC_ScanStart(&g_adc0_ctrl);
}
该中断的任务比较简单,就是在ADC转换完成后,读取adc的值存储到g_adcvalue变量中,并重新开启adc转换。 同时提供外部一个接口getadcvalue用以获取g_adcvalue中的值。
4.4 功能展示
我们使用串口打印adc的值,利用串口工具将数值通过波形展示,
蓝色的线条为输出的历史波形,目前只做了保存1s。
总结
参加这项活动还是非常开心的,能够和很多大佬一起交流,得捷提供的开发板不得不说性能非常强大,我只是简单测试了一些基本的功能,希望以后能够好好利用这款开发板,进一步学习这块开发板。
视频链接
【Follow me第二季第3期】任务汇总-【Follow me第二季第3期】任务汇总-EEWORLD大学堂
代码链接
【 Follow me第二季第3期】任务1-嵌入式开发相关资料下载-EEWORLD下载中心
【Follow me第二季第3期】任务2、3-嵌入式开发相关资料下载-EEWORLD下载中心
【Follow me第二季第3期】任务4-嵌入式开发相关资料下载-EEWORLD下载中心
- 2024-12-03
-
加入了学习《【Follow me第二季第3期】扩展任务---EK_RA6M5函数信号发生器》,观看 【Follow me第二季第3期】扩展任务---EK_RA6M5函数信号发生器
- 2024-11-28
-
加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门
-
加入了学习《Follow me第二季第3期演示视频》,观看 Follow me第二季第3期演示视频
- 2024-11-22
-
回复了主题帖:
入围名单公布:嵌入式工程师AI挑战营(进阶)的挑战者们,领取板卡啦
补充内容:
InsightFace简述: InsightFace 是一个开源的人脸识别算法,用于人脸检测、定位、对齐和特征提取并识别,具备高精度和高性能的特点。使用5个特征点进行人脸检测,这使得它在资源消耗上相对较低,适合在嵌入式系统上部署。
部署实现思路:
1. 首先就是选择合适的模型,下载现成的预训练好的开源模型,可以直接使用或者对使用自己的数据二次训练。
2. 接着就是RKNN Toolkit,将 InsightFace 的 PyTorch 或 MXNet 模型转换为 RV1106 可运行的 RKNN 模型
3. 搭建开发环境,确保 RV1106 开发板可以使用常用的一些库,如 Python、OpenCV、pytorch等。确保pytorch的版本大于1.6或者3.x。
4. 用 InsightFace 的人脸检测算法如RetinaFace、SCRFD等方法检测视频流中的所有人脸,并对检测到的人脸进行对齐,支持的方法有SDUNets、SimpleRegression,最后就是用人脸识别模型输出特征,并于保存的人脸数据比对,判断人脸数据是否通过验证。
- 2024-11-21
-
回复了主题帖:
入围名单公布:嵌入式工程师AI挑战营(进阶)的挑战者们,领取板卡啦
个人信息已确认,领取板卡,可继续完成任务。
-
回复了主题帖:
入围名单公布:嵌入式工程师AI挑战营(进阶)的挑战者们,领取板卡啦
补充内容:
InsightFace简述: InsightFace 是一个开源的人脸识别算法,用于人脸检测、对齐和特征提取,具备高精度和高性能的特点。InsightFace 支持2D和3D人脸分析,并在多个基准测试中达到了先进的性能。使用5个特征点进行人脸检测,这使得它在资源消耗上相对较低,适合在嵌入式系统上部署。
部署实现思路:
1. 选择合适的模型:根据 RV1106 的计算能力和 NPU 兼容性选择轻量化的模型。
2. 模型转换:使用 Rockchip 提供的工具链(如 RKNN Toolkit),将 InsightFace 的 PyTorch 或 MXNet 模型转换为 RV1106 可运行的 RKNN 模型
3. 开发环境搭建:确保 RV1106 开发板已经安装了必要的依赖,如 Python、OpenCV、NumPy 等。
4. 人脸检测与对齐:利用 InsightFace 的人脸检测算法检测视频流中的所有人脸,并对检测到的人脸进行对齐。
5. 结果展示:将识别结果和轨迹追踪信息实时展示在界面上,或者通过其他方式输出。
- 2024-11-20
-
回复了主题帖:
嵌入式工程师AI挑战营(进阶):在RV1106部署InsightFace算法的多人实时人脸识别实战
申请理由:
对InsightFace人脸识别项目非常感兴趣。同时对于luckyfox提供的开发板,我也被他的小尺寸十分吸引。我觉得不仅仅是用来用来人脸识别,这块开发板还可以用在其他各行各业。我再硕士期间学习过深度学习和python,一直都是研究模型的训练评估,但是没有部署的经验,希望借此机会,对深度学习有进一步认识。
打算部署的应用:
我计划在RV1106 Linux开发板上部署一个智能门禁系统,该系统能够实时识别进入人员的身份,并根据预设的权限控制门的开启。