本帖最后由 EPTmachine 于 2025-1-6 22:14 编辑
本次Follow Me活动使用的开发板为Renesas的EK_RA6M5开发板。主要实现以下几个任务。
入门任务:搭建环境,下载调试示例程序,Blink,按键;
基础任务:quad-spi flash和octo-spi flash配置及读写速度测试;DAC配置生成波形及性能测试;
进阶任务:示例程序中新增命令打印信息;
扩展任务:设计一个类似信号发生器功能的例程。可在示例程序上修改。通过命令或按键,设置DAC输出波形,可通过flash存储历史波形等信息。
播放器加载失败: 未检测到Flash Player,请到
安装
output
入门任务:
任务相关的内容【Follow me第二季第3期】1入门任务:搭建环境,下载调试示例程序,Blink,按键
入门任务通过调用bsp中的延时函数,实现周期控制,在延时完成后,改变连接LED的IO的输出状态,从事而实现LED灯闪烁的功能。连接按键的IO配置为中断输入,接收外部的中断信号,设置相应的标志变量,在主程序中检测对应的标志变量,改变延时的时间长短,影响LED灯的闪烁频率。程序的流程图如下:
中断程序检测部分代码(位于“button.c中”):
void button1_callback(external_irq_callback_args_t *p_args)
{
if(USER_SW1_IRQ_NUMBER == p_args->channel)
{
g_sw1_press = true;
}
}
void button2_callback(external_irq_callback_args_t *p_args)
{
if(USER_SW2_IRQ_NUMBER == p_args->channel)
{
g_sw2_press = true;
}
}
主程序中检测按键状态变量和控制LED输出引脚的代码如下(位于“hal_entry.c中”)
while (1)
{
if(true == g_sw1_press)
{
g_sw1_press = false;
err = R_IOPORT_PinRead(&g_ioport_ctrl, (bsp_io_port_pin_t)leds.p_leds[0], &led1_level);
if (FSP_SUCCESS != err)
{
button_deinit();
}
led1_level ^= BSP_IO_LEVEL_HIGH;
err = R_IOPORT_PinWrite(&g_ioport_ctrl, (bsp_io_port_pin_t)leds.p_leds[0], led1_level);
if (FSP_SUCCESS != err)
{
button_deinit();
}
}
if(true == g_sw2_press)
{
g_sw2_press = false;
switch(feq_level)
{
case slow_speed:
feq_level=normal_speed;
freq_in_hz=1;
break;
case normal_speed:
feq_level=high_speed;
freq_in_hz=2;
break;
case high_speed:
feq_level=slow_speed;
freq_in_hz=5;
break;
}
delay = bsp_delay_units / freq_in_hz;
}
err = R_IOPORT_PinWrite(&g_ioport_ctrl, (bsp_io_port_pin_t)leds.p_leds[1], led2_level);
if (BSP_IO_LEVEL_LOW == led2_level)
{
led2_level = BSP_IO_LEVEL_HIGH;
}
else
{
led2_level = BSP_IO_LEVEL_LOW;
}
R_BSP_SoftwareDelay(delay, bsp_delay_units);
}
工程的效果如图
e2studio的开发环境功能多样,在熟悉各部分功能的使用后,可以借助FSP工具、Deverlop Asssitant代码提示加快开发进度。
基础任务:
任务相关的内容【Follow me第二季第三期】基础任务:quad spi、octo spi、dac和定时器使用
板载的外设很多,quad spi flash和octo spi flash扩展了开发板的存储空间,可以存储更多的程序和数据,实现复杂的应用,同时FSP配置工具中可以快捷地设定相关外设的驱动代码,方便外设的使用减少开发人员的工作量。
测量quad spi flash和octo spi flash 的基本思路为:
相关的代码如下(位于gpt_timer.c中):
fsp_err_t start_gpt_timer (timer_ctrl_t * const p_timer_ctl)
{
fsp_err_t err = FSP_SUCCESS;
err = R_GPT_Start(p_timer_ctl);
if (FSP_SUCCESS != err)
{
* Since, cleanup for GPT open is done in start_gpt_timer,Hence cleanup is not required */
APP_ERR_PRINT ("\r\n ** R_GPT_Start API failed ** \r\n");
}
return err;
}
void report_timer_count(void)
{
fsp_err_t err = FSP_SUCCESS;
timer_status_t timer_info;
uint32_t time_counter=0;
err=stop_gpt_timer (&g_timer_ctrl);
err=get_gpt_timer_status(&g_timer_ctrl,&timer_info);
err=reset_gpt_timer (&g_timer_ctrl);
time_counter=timer_info.counter;
APP_PRINT("\nOperation on Flash Completed Successfully,Time Cost: %d ticks under %d HZ \r\n",time_counter,timer_freq);
}
实际运行时,可以得到OSPI在SPI模式、SOPI、DOPI模式下的读写操作耗时。通过Segger RTT工具输出的定时器计数频率在100MHz时,读写4096字节数据的测试信息如下。
SPI模式下写时间耗时3.26ms,读时间耗时5.1ms
SOPI模式下写时间耗时2.6ms,读时间耗时1.6ms
DOPI模式下写时间耗时2.5ms,读时间耗时1.5ms
使用DAC输出方波,配置DAC和定时器,在定时器中断中改变DAC输出寄存器的值,从而输出周期为定时器频率一半的方波,大致的流程如下:
主函数中对定时器和DAC进行初始化(位于“hal_entry.c中”)
err = R_DAC_Open (&g_dac1_ctrl, &g_dac1_cfg);
err = R_DAC_Write (&g_dac1_ctrl, DAC_MIN_VAL);
err = R_DAC_Start (&g_dac1_ctrl);
err = init_gpt_timer(&g_timer0_ctrl, &g_timer0_cfg);
err = start_gpt_timer(&g_timer0_ctrl);
回调函数每次调用会修改DAC的数据寄存器中的值,改变DAC输出的电压值,从而生成方波。(位于“gpt_timer.c中”)
void timer0_callback(timer_callback_args_t *p_args)
{
if(TIMER_EVENT_CYCLE_END==p_args->event)
{
if(dac_output==DAC_MIN_VAL)
{
dac_output=DAC_MAX_VAL;
}
else if(dac_output==DAC_MAX_VAL)
{
dac_output=DAC_MIN_VAL;
}
R_DAC_Write (&g_dac1_ctrl,dac_output);
}
}
使用示波器采集输出端的信号,得到的是500Hz,幅值为3.3V的方波。
进阶任务:
任务相关的内容【Follow me第二季第三期】进阶任务:通过USB发送指令修改QSPI中的数据
USB设备可以设定端点类型为CDC设备,从而实现与电脑的通讯,建立通讯后后,通过控制台向开发板发送数据,从而实现数据交互和指令控制。
根据接受到的数据的不同,提示用户输入相关的输入,从而实现对qspi flash中存储的数据的修改。
示例程序的流程图如下
添加指令的核心在与添加可以被识别的菜单选项(menu option),指令的识别和执行的流程图如下:
实现上述功能的代码在工程的menu_dds_record.c和menu_dds_record.h中,其中的dds_record_menu()为处理Flash中数据的具体流程
test_fn dds_record_menu(void)
{
int8_t c = -1;
bool_t valid_dds_index = false;
bool_t valid_dds_type = false;
bool_t valid_dds_freq = false;
int32_t block_sz_ndx = 0;
int32_t block_sz_limit = (INPUT_BUFFER - 2);
uint8_t dds_index;
uint8_t dds_type;
uint32_t dds_freq;
uint32_t value=0;
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
print_to_console((void*)s_print_buffer);
sprintf (s_print_buffer, MODULE_NAME, g_selected_menu);
print_to_console((void*)s_print_buffer);
for(uint8_t index=0;index<10;index++)
{
sprintf(s_print_buffer,"\r\n%d.type:%d, freq:%lu, amp:%d, offset:%d", index,
dds_record_ins.dds_items[index].waveType,
dds_record_ins.dds_items[index].freq,
dds_record_ins.dds_items[index].amp,
dds_record_ins.dds_items[index].offset);
print_to_console((void*)s_print_buffer);
}
sprintf (s_print_buffer, DDSINDEX_OPTIONS);
print_to_console((void*)s_print_buffer);
while (false == valid_dds_index)
{
c = -1;
block_sz_ndx = 0;
memset(&s_block_sz_str, 0, INPUT_BUFFER);
while ((CONNECTION_ABORT_CRTL != c))
{
c = input_from_console();
if (block_sz_ndx < block_sz_limit)
{
s_block_sz_str[block_sz_ndx] = (char_t)c;
block_sz_ndx++;
}
else
{
s_block_sz_str[block_sz_ndx] = MENU_ENTER_RESPONSE_CRTL;
c = MENU_ENTER_RESPONSE_CRTL;
break;
}
if (MENU_EXIT_CRTL == c)
{
valid_dds_index = true;
dds_index = 0;
break;
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
break;
}
if (CARRAGE_RETURN != c)
{
sprintf(s_print_buffer, "%c", (char_t)c);
print_to_console((void*)s_print_buffer);
}
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
value = validate_user_input(&s_block_sz_str[0]);
}
vTaskDelay(10);
dds_index=(uint8_t)value;
switch (value)
{
default:
{
valid_dds_index = true;
}
}
}
sprintf (s_print_buffer, DDSTYPE_OPTIONS,dds_index);
print_to_console((void*)s_print_buffer);
while (false == valid_dds_type)
{
c = -1;
block_sz_ndx = 0;
memset(&s_block_sz_str, 0, INPUT_BUFFER);
while ((CONNECTION_ABORT_CRTL != c))
{
c = input_from_console();
if (block_sz_ndx < block_sz_limit)
{
s_block_sz_str[block_sz_ndx] = (char_t)c;
block_sz_ndx++;
}
else
{
s_block_sz_str[block_sz_ndx] = MENU_ENTER_RESPONSE_CRTL;
c = MENU_ENTER_RESPONSE_CRTL;
break;
}
if (MENU_EXIT_CRTL == c)
{
valid_dds_type = true;
dds_type = 0;
break;
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
break;
}
if (CARRAGE_RETURN != c)
{
sprintf(s_print_buffer, "%c", (char_t)c);
print_to_console((void*)s_print_buffer);
}
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
value = validate_user_input(&s_block_sz_str[0]);
}
vTaskDelay(10);
dds_type=(uint8_t)value;
switch (value)
{
default:
{
valid_dds_type = true;
}
}
}
sprintf (s_print_buffer, DDSFREQ_OPTIONS,dds_index);
print_to_console((void*)s_print_buffer);
while (false == valid_dds_freq)
{
c = -1;
block_sz_ndx = 0;
memset(&s_block_sz_str, 0, INPUT_BUFFER);
while ((CONNECTION_ABORT_CRTL != c))
{
c = input_from_console();
if (block_sz_ndx < block_sz_limit)
{
s_block_sz_str[block_sz_ndx] = (char_t)c;
block_sz_ndx++;
}
else
{
s_block_sz_str[block_sz_ndx] = MENU_ENTER_RESPONSE_CRTL;
c = MENU_ENTER_RESPONSE_CRTL;
break;
}
if (MENU_EXIT_CRTL == c)
{
valid_dds_freq = true;
dds_freq = 0;
break;
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
break;
}
if (CARRAGE_RETURN != c)
{
sprintf(s_print_buffer, "%c", (char_t)c);
print_to_console((void*)s_print_buffer);
}
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
value = validate_user_input(&s_block_sz_str[0]);
}
vTaskDelay(10);
dds_freq=value;
switch (value)
{
default:
{
valid_dds_freq = true;
}
}
}
dds_record_ins.dds_items[dds_index].waveType=dds_type;
dds_record_ins.dds_items[dds_index].freq=dds_freq;
dds_record_ins.dds_items[dds_index].offset=0;
dds_record_ins.dds_items[dds_index].amp=30;
for(uint8_t index=0;index<10;index++)
{
sprintf(s_print_buffer,"\r\n%d.type:%d, freq:%ld, amp:%d, offset:%d", index,
dds_record_ins.dds_items[index].waveType,
dds_record_ins.dds_items[index].freq,
dds_record_ins.dds_items[index].amp,
dds_record_ins.dds_items[index].offset);
print_to_console((void*)s_print_buffer);
}
xSemaphoreGive(g_store_dds_semaphore);
sprintf(s_print_buffer, MENU_RETURN_INFO);
print_to_console((void*)s_print_buffer);
while ((CONNECTION_ABORT_CRTL != c))
{
if ((MENU_EXIT_CRTL == c) || (0x00 == c))
{
break;
}
c = input_from_console();
}
return (0);
}
在menu_main中定义了如何进行指令的识别和执行,s_menu_items中包含上述两个回调函数,当用户输入正确的数字,程序就会调用相应的回调函数,实现对应的功能。
static st_menu_fn_tbl_t s_menu_items[] =
{
{"Text display" , text_display_menu},
{"DDS record edit" , dds_record_menu},
{"", NULL }
};
...
while ((0 != c))
{
c = input_from_console ();
if (0 != c)
{
c = (int8_t) (c - '0');
g_selected_menu = c;
if ((c > 0) && (c <= menu_limit))
{
s_menu_items[c - 1].p_func ();
break;
}
}
}
工程编译并下载后,效果运行的效果如下所示。能够对Flash中存储的数据进行读写管理。
扩展任务:
任务相关的内容【Follow me第二季第三期】扩展任务:设计一个类似信号发生器功能的例程
结合之前的qspi flash储存和usb 指令任务,添加dds模块后,即可以实现简易的DDS信号发生器。
程序逻辑上分为指令解析执行和DDS信号产生和输出。
指令解析执行的流程为:
指令解析执行部分的代码大部分与进阶任务相同,这里不多做介绍,其中不同的是DDS数据的选择并产生波形,代码位于menu_dds_ctrl.c
中
test_fn dds_ctrl_menu(void)
{
int8_t c = -1;
uint8_t dds_index=0;
bool_t valid_dds_index = false;
int32_t block_sz_ndx = 0;
int32_t block_sz_limit = (INPUT_BUFFER - 2);
uint32_t value=0;
dds_param_t cur_dds;
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
print_to_console((void*)s_print_buffer);
sprintf (s_print_buffer, MODULE_NAME, g_selected_menu);
print_to_console((void*)s_print_buffer);
for(uint8_t index=0;index<10;index++)
{
sprintf(s_print_buffer,"\r\n%d.type:%d, freq:%lu, amp:%d, offset:%d", index,
dds_record_ins.dds_items[index].waveType,
dds_record_ins.dds_items[index].freq,
dds_record_ins.dds_items[index].amp,
dds_record_ins.dds_items[index].offset);
print_to_console((void*)s_print_buffer);
}
sprintf (s_print_buffer, DDSINDEX_SELECT);
print_to_console((void*)s_print_buffer);
while (false == valid_dds_index)
{
c = -1;
block_sz_ndx = 0;
memset(&s_block_sz_str, 0, INPUT_BUFFER);
while ((CONNECTION_ABORT_CRTL != c))
{
c = input_from_console();
if (block_sz_ndx < block_sz_limit)
{
s_block_sz_str[block_sz_ndx] = (char_t)c;
block_sz_ndx++;
}
else
{
s_block_sz_str[block_sz_ndx] = MENU_ENTER_RESPONSE_CRTL;
c = MENU_ENTER_RESPONSE_CRTL;
break;
}
if (MENU_EXIT_CRTL == c)
{
valid_dds_index = true;
dds_index = 0;
break;
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
break;
}
if (CARRAGE_RETURN != c)
{
sprintf(s_print_buffer, "%c", (char_t)c);
print_to_console((void*)s_print_buffer);
}
}
if (MENU_ENTER_RESPONSE_CRTL == c)
{
value = validate_user_input(&s_block_sz_str[0]);
}
vTaskDelay(10);
dds_index=(uint8_t)value;
switch (value)
{
default:
{
valid_dds_index = true;
}
}
}
cur_dds=dds_record_get(dds_index);
DDS_setWaveParams(cur_dds.freq, 30, 0, cur_dds.waveType);
sprintf (s_print_buffer, DDS_GEN);
print_to_console((void*)s_print_buffer);
sprintf(s_print_buffer, MENU_RETURN_INFO);
print_to_console((void*)s_print_buffer);
while ((CONNECTION_ABORT_CRTL != c))
{
if ((MENU_EXIT_CRTL == c) || (0x00 == c))
{
break;
}
c = input_from_console();
}
return (0);
}
其中的DDS_setWaveParams(cur_dds.freq, 30, 0, cur_dds.waveType);
即为DDS信号产生和输出的函数。
DDS信号产生和输出的流程为:
相关的代码位于dds.c
中,函数DDS_setWaveParams()
用于产生DDS信号,getNewWaveLUT()
用于生成查找表
void DDS_setWaveParams(uint32_t freq, uint8_t amptitude, int8_t offset, uint8_t type)
{
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err=R_GPT_Stop(&g_timer_dds_ctrl);
R_DMAC_Disable(&g_dma_dds_ctrl);
switch(type)
{
case SINE_WAVE:
dds.waveType = SINE_WAVE;
break;
case SQUARE_WAVE:
dds.waveType = SQUARE_WAVE;
break;
case TRIANGLE_WAVE:
dds.waveType = TRIANGLE_WAVE;
break;
default:
break;
}
dds.freq = freq;
dds.amp = amptitude;
if (freq >= 100 && freq < 1000)
{
R_GPT_PeriodSet(&g_timer_dds_ctrl, 1000-1);
dds.lutLen = (uint32_t)(100000 / freq);
getNewWaveLUT(dds.lutLen, dds.waveType, dds.amp, dds.offset);
}
else if (freq >= 1000 && freq < 10000)
{
R_GPT_PeriodSet(&g_timer_dds_ctrl, 100-1);
dds.lutLen = (uint32_t)(1000000 / freq);
getNewWaveLUT(dds.lutLen, dds.waveType, dds.amp, dds.offset);
}
else if (freq >= 10000 && freq < 100000)
{
R_GPT_PeriodSet(&g_timer_dds_ctrl, 50-1);
dds.lutLen = (uint32_t)(2000000 / freq);
getNewWaveLUT(dds.lutLen, dds.waveType, dds.amp, dds.offset);
}
g_dma_dds_cfg.p_info->length=dds.lutLen;
g_dma_dds_cfg.p_info->p_src=dds_lut;
g_dma_dds_cfg.p_info->p_dest=&g_dac0_ctrl.p_reg->DADR[g_dac0_ctrl.channel_index];
R_DMAC_Reconfigure(&g_dma_dds_ctrl, g_dma_dds_cfg.p_info);
R_DMAC_Enable(&g_dma_dds_ctrl);
R_GPT_Start(&g_timer_dds_ctrl);
if (dds.enable)
{
}
}
void getNewWaveLUT(uint32_t length, uint8_t type, uint8_t amp, int8_t offset)
{
uint16_t a_offset_value = DAC_AMP - (int32_t)DAC_AMP * offset / DDS_MAX_AMP;
if (type == SINE_WAVE)
{
float sin_step = 2.0f * 3.14159f / (float)(length-1);
for (uint16_t i = 0; i < length; i++)
{
dds_lut<i> = (uint16_t)(a_offset_value - (DAC_AMP * sinf(sin_step*(float)i) * amp / DDS_MAX_AMP));
}
}
else if (type == SQUARE_WAVE)
{
for(uint16_t i = 0; i < length / 2; i++)
{
dds_lut<i> = a_offset_value - DAC_AMP * amp / DDS_MAX_AMP;
dds_lut[i + (length / 2)] = a_offset_value + DAC_AMP * amp / DDS_MAX_AMP;
}
}
else if (type == TRIANGLE_WAVE)
{
uint16_t tri_step = DAC_AMP * 2 * amp / DDS_MAX_AMP / (length/2);
for(uint16_t i = 0; i < length / 2; i++)
{
dds_lut<i> = a_offset_value - DAC_AMP * amp / DDS_MAX_AMP + tri_step*i;
dds_lut[length - i - 1] = dds_lut<i>;
}
}
}
实验效果:
产生正弦波
产生方波
产生三角波
总结
开发板的硬件资源和示例代码很多,方便开发者理解如何使用芯片的功能。同时e2studio的开发工具辅助开发者快速搭建应用程序,加快开发进度。
任务代码: