本帖最后由 CoderX9527 于 2024-12-15 16:56 编辑
致谢
非常感谢 EEWORLD && DigiKey 联合举办的 FollowMe 活动,我非常幸运地参与了此次活动,学到了不少东西。以前没接触过 Renesas MCU,也没有接触过 Renesas 的开发环境,经过这次的学习,我学会了 DAC 输出波形,通过 QSPI 读写 Flash。受益良多。
视频
代码
任务简介
此次活动总共有4个任务:
- 入门任务:搭建环境,下载调试例程,Blink,按键;
- 基础任务:quid-spi flash 和 octo-spi flash 配置以及读写速度测试;DAC配置生成波形以及性能测试;
- 进阶任务:示例程序中新增命令打印信息;
- 扩展任务:设计一个类似信号发生器功能的例程。通过命令或者按键,设置 DAC 输出波形,可通过 flash 存储历史波形等信息。
物料
此次活动仅使用一个物料,即 EK-RA6M5 板卡。
1. 入门任务
其中入门任务,已经在如下帖子中分享我的实践: https://bbs.eeworld.com.cn/thread-1301289-1-1.html
此次作品提交贴分三个部分,分别介绍基础任务、进阶任务和扩展任务。
2. 基础任务:QSPI-Flash 和 OSPI-Flash 速度测试,以及 DAC 配置生成波形和性能测试
QSPI-Flash 和 OSPI-Flash 速度测试
QSPI-Flash和OSPI-Flash 速度测试在 quick_start 工程中已经有了,这里理清楚它们的实现思路并实测演示。
实现思路
从下面的菜单项可以看到QSPI-Flash和OSPI-Flash速度测试的入口函数是 ext_display_menu()
串口打印输出如下,用户输入整数为2的倍数,最大值为64,指定文本块大小,然后开始测试。
主流程
OSPI-Flash 读写测试流程
QSPI-Flash 写测试流程
QSPI-Flash 读测试流程
速度测试
块大小 2KB
块大小 4KB
块大小 8KB
块大小 16KB
块大小 32KB
块大小 64KB
总结
从上图可知:
- 无论QSPI还是OSPI,块大小翻倍,读写耗时都会翻倍,大约是2倍;
- 同样的块大小写操作,OSPI耗时基本是 QSPI 的一半,即速度大约是 QSPI 的2倍,符合预期;
- 同样的块大小读操作,OSPI耗时基本是 QSPI 的1/6,即速度大约是 QSPI的6倍。
DAC配置生成波形和性能测试
RA6M5 提供了一个带输出放大器的 12bit DAC,有两个输出通道。DA0 管脚是 DAC的通道0输出管脚,DA1 是 DAC 的通道1输出管脚。
三角波
由于 DAC 位宽是12位,数值范围是[0, 4095],因此三角波一个周期内的数值变化趋势是 0逐步增加到 4095,然后又逐步减少到0,周期循环。
这里为此单独写了一个生成三角波的模块,每次调用函数 triangle_wave_get_next() 都会获取三角波中的下一个点。
C++
typedef struct {
int16_t step; /**< 斜率 */
int32_t val;
} triangle_wave_t;
static triangle_wave_t m_triangle_wave = {
.step = 1 // 默认斜率为1
};
/**
* @brief 设置三角波斜率,可选值 [-2048, 2048]
*
* @param step
*/
void triangle_wave_cfg(int16_t step)
{
if ((step < -2048) || (step > 2048)) {
return;
}
m_triangle_wave.step = step;
}
/**
* @brief 获取三角波中的下一个点
*
* @return int32_t
*/
int32_t triangle_wave_get_next(void)
{
int32_t temp = 0;
temp = m_triangle_wave.val + m_triangle_wave.step;
if (m_triangle_wave.step >= 0) {
if (temp > 4095) {
m_triangle_wave.step = 0 - m_triangle_wave.step; // step 变负数
}
} else {
if (temp < 0) {
m_triangle_wave.step = 0 - m_triangle_wave.step; // step 变正数
}
}
m_triangle_wave.val += m_triangle_wave.step;
return m_triangle_wave.val;
} |
DAC 配置
新增Stack--DAC
- 双击工程的配置文件 configuration.xml 文件,打开配置工具;
- New Stack 展开子菜单,选择 DAC
g_dac0 属性
- 打开 Properties 窗口,点击 HAL/Common Stacks 窗口中的 g_dac0 DAC (r_dac) ,然后就可以在下方属性窗口看到 g_dac0 的各个设置。
- 属性中可知 DAC 通道0对应的实例对象名字为 g_dac0,数据格式:Right Justifed,即数据右对齐,其他选项都是默认值;
- 从 Pins 可以看到 DA0 没有对应管脚,即(3)是 None,我们单击(4)处的箭头就可以跳转到 Pin Configuration 窗口,配置 DA0 管脚。
展开的 Pin Configuration 窗口中,可以看到 DAC0 的 管脚还是 None,我们修改为 P014,然而还是红色标注,说明配置有错误。
DA0 配置 P014 为输出管脚,红色表示管脚有错误或冲突。
找到 ADC 中的 ADC0,可知通道 AN012 占用了 P014 管脚,修改它为 None,即可解决 DAC0 配置 P014 管脚冲突问题。
确认 DAC0 的 P014 管脚配置没有问题之后,还需要点击 Generate Project Content 重新生成工程。
DAC 输出三角波
在函数 common_init() 中添加如下代码,初始化 DAC
在 gpt_blue_callback() 中添加一行 R_DAC_Write(&g_dac0_ctrl, triangle_wave_get()) 即可输出三角波。
示波器测量
心得体会
- 熟悉了定时器操作;
- 熟悉了QSPI-Flash, OSPI-Flash 的操作,实测对比读写速度,对于两者的性能有更深的认识;
- 熟悉了DAC模块,如何在 e2studio 中添加并配置 DAC 模块,示波器测试 DAC 输出,加深了对 DAC 的认识;
- DAC输出波形如此简单,以前以为很难,其实就是往 DAC 输出一个数值即可。
3. 进阶任务目标:示例程序中新增命令打印信息
显示命令的代码
从 main() 开始,一路找到显示菜单的函数,流程图如下:
菜单数组 s_menu_items
菜单数组如下,每一个成员都是一个结构体 st_menu_fn_tbl_t,成员 p_name 表示显示的菜单名字,成员 p_func 表示此菜单的执行函数。
C++
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 }
}; |
如第一个菜单项,菜单名字为 "Kit Information",菜单的执行函数为 kis_display_menu()。这个菜单执行函数打印开发板的基本信息,包括板卡名字,part number,128-比他UID,芯片温度,蓝色LED闪烁频率、亮度等级等。
我新增一个命令,菜单名字为 "Welcome",菜单执行函数为 kit_welcome(),此菜单打印MCU的温度信息。
菜单数组更新
C++
/* Table of menu functions */
static st_menu_fn_tbl_t s_menu_items[] =
{
{"Welcome" , kit_welcome}, // 新增命令
{"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 }
}; |
kit_welcome() 实现
此函数实现如下,最关键的就是第10行代码,把字符串输出到 s_print_buffer,然后调用函数 print_to_console((void *)s_print_buffer) 输出到终端。
C++
test_fn kit_welcome(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, "\r\nWelcome, EEWORLD & DigiKey FollowMe 2-3 by CoderX9527\r\n");
/* 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 when treating signed char_t array as as unsigned */
sprintf (s_print_buffer, MENU_RETURN_INFO);
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;
}
}
xEventGroupClearBits (g_update_console_event, STATUS_DISPLAY_MENU_KIS);
return (0);
} |
运行演示
按下1进入 Welcome 命令界面,如下图所示:
按下空格键返回到主菜单。
心得体会
- 通过串口输出打印信息也可以玩的很花,在打印的字符串中加入特殊的控制字符,例如 \x1b[2m 等可以控制输出的格式,以及输出文字的颜色,但是需要串口中断支持;
4. 扩展任务:设计一个类似信号发生器功能的历程。可以在示例程序上修改。通过命令或者按键,设置DAC输出波形,可以通过 Flash 存储历史波形等信息。
设计思路
- 信号发生器,支持输出三角波、正弦波。
- 命令界面,t -- 三角波,s -- 正弦波,w -- 保存波形类型到Flash,r -- 从 Flash 读取波形类型。
实现步骤
- 设计2种波形,分别是三角波、正弦波,能输出对应的波形;
- 菜单中增加命令,进入信号发生器菜单界面,可选择子菜单项,总共4个选项;
- 分别实现4个子菜单项;
- 分别验证4个子菜单项;
菜单设计
在主菜单中新增一个菜单项,名字为 Signal Generator,执行函数为 kit_signal_generator()。
C
/* Table of menu functions */
static st_menu_fn_tbl_t s_menu_items[] =
{
{"Welcome" , kit_welcome},
{"Signal Generator" , kit_signal_generator}, // 信号发生器
{"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 }
}; |
函数 kit_signal_generator()
执行函数在串口打印如下信息,用户输入
C
test_fn kit_signal_generator(void)
{
int8_t c = -1;
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);
// 此处打印各个菜单项
sprintf(s_print_buffer, OPT_HELP);
print_to_console(s_print_buffer);
sprintf(s_print_buffer, OPT_TRIANGLE);
print_to_console(s_print_buffer);
sprintf(s_print_buffer, OPT_SINE);
print_to_console(s_print_buffer);
sprintf(s_print_buffer, OPT_WRITE);
print_to_console(s_print_buffer);
sprintf(s_print_buffer, OPT_READ);
print_to_console(s_print_buffer);
sprintf(s_print_buffer, MENU_RETURN_INFO);
print_to_console((void*)s_print_buffer);
while (CONNECTION_ABORT_CRTL != c) {
c = input_from_console();
if (c == 't') {
triangle_wave_cfg(20);
wave_type_set(WAVE_TYPE_TRIANGLE);
print_to_console("t --> Force wave type: Triangle\r\n");
continue;
}
if (c == 's') {
wave_type_set(WAVE_TYPE_SINE);
print_to_console("s --> Force wave type: Sine\r\n");
continue;
}
if (c == 'w') {
sprintf(s_print_buffer, "w --> Save wave type: %u \r\n", (uint32_t)wave_type_get());
print_to_console(s_print_buffer);
save_wave_type(wave_type_get());
continue;
}
if (c == 'r') {
uint32_t wt = 0;
if (restore_wave_type(&wt) == FSP_SUCCESS) {
wave_type_set(wt);
}
sprintf(s_print_buffer, "r --> Restore wave type: %u \r\n", (uint32_t)wave_type_get());
print_to_console(s_print_buffer);
continue;
}
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c)) {
break;
}
}
xEventGroupClearBits(g_update_console_event, STATUS_DISPLAY_MENU_KIS);
return (0);
} |
产生三角波
产生三角波的代码见入门任务,这里仅粘贴代码。重点在函数 triangle_wave_get_next(),每调用一次即可得到下一个点。
C
typedef struct {
int16_t step; /**< 斜率 */
int32_t val;
} triangle_wave_t;
static triangle_wave_t m_triangle_wave = {
.step = 1 // 默认斜率为1
};
/**
* @brief 设置三角波斜率,可选值 [-2048, 2048]
*
* @param step
*/
void triangle_wave_cfg(int16_t step)
{
if ((step < -2048) || (step > 2048)) {
return;
}
m_triangle_wave.step = step;
}
/**
* @brief 获取三角波中的下一个点
*
* @return int32_t
*/
int32_t triangle_wave_get_next(void)
{
int32_t temp = 0;
temp = m_triangle_wave.val + m_triangle_wave.step;
if (m_triangle_wave.step >= 0) {
if (temp > 4095) {
m_triangle_wave.step = 0 - m_triangle_wave.step; // step 变负数
}
} else {
if (temp < 0) {
m_triangle_wave.step = 0 - m_triangle_wave.step; // step 变正数
}
}
m_triangle_wave.val += m_triangle_wave.step;
return m_triangle_wave.val;
} |
产生正弦波
产生正弦波的代码也很简单,只需要调用 sine_wave_get_next() 即可获取下一个点。
C
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define SAMPLE_N (500) // sin 周期 500 个点
#define X_FACT ((2 * M_PI) / SAMPLE_N) // X系数
static int sine_t = 0; // 时间变量
double sine_wave_get_next(void)
{
double y = (sin(X_FACT * sine_t) + 1) / 2 * 4095;
sine_t++;
return y;
} |
保存波形、读取波形
QSPI-Flash 的读写操作参见 QSPI-Flash 读写速度测试,修改 qspi_write_test() 和 qspi_read_test() 函数即可分别实现写 Flash 和读 Flash 的操作。
保存波形 save_wave_type()
入口参数 wt 表示波形类型,参见 wave_type_e。
- 先调用 qpi_init() 初始化 QSPI ;
- R_QSPI_Erase() 擦除一个扇区;
- R_QSPI_Write() 把 wt 写入到第一个扇区首地址上;
- deinit_qspi() 反初始化 QSPI
C
/**
* @brief 保存波形类型到 Flash 上。
*
* @param wt 参见 wave_type_e
* @return
*/
static fsp_err_t save_wave_type(uint32_t wt)
{
fsp_err_t fsp_err = FSP_SUCCESS;
fsp_err_t err = FSP_SUCCESS;
spi_flash_protocol_t current_spi_mode;
uint32_t block_size = 4; // 4KB
/* 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;
// NOTE: Erase Sector
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");
print_to_console(s_print_buffer);
} else {
err = get_flash_status();
if (FSP_SUCCESS != err) {
sprintf(s_print_buffer, "Failed to get status for QSPI operation\r\n");
print_to_console(s_print_buffer);
}
/* Verify the erased block data */
for (uint32_t 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++;
}
/* 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;
// NOTE: Write
fsp_err = R_QSPI_Write(&g_qspi_ctrl, &wt, p_mem_addr, sizeof(wt));
/* close QSPI module */
deinit_qspi(current_spi_mode);
/* Handle error */
if (FSP_SUCCESS != fsp_err) {
/* Fatal error */
SYSTEM_ERROR
}
return (fsp_err);
} |
读取波形 restore_wave_type()
从 Flash 读取波形类型保存到入口参数 wt 中。
- qpi_init() 初始化 QSPI;
- memcpy() 读取 QSPI_DEVICE_START_ADDRESS 前1个word 并保存到 wt 地址上;
- deinit_qspi() 反初始化 QSPI;
C
/**
* @brief 从 Flash 读取 wave_type
*
* @param wt
* @return 0 表示成功,其他值表示失败
*/
static fsp_err_t restore_wave_type(uint32_t* wt)
{
fsp_err_t err = FSP_SUCCESS;
spi_flash_protocol_t current_spi_mode;
uint8_t* p_mem_addr;
/* The comms mode of the FLASH device is EXTENDED_SPI by default */
current_spi_mode = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init();
if (FSP_SUCCESS == err) {
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
// NOTE: Read wave type from Flash
p_mem_addr = (uint8_t*)QSPI_DEVICE_START_ADDRESS;
memcpy((void*)wt, p_mem_addr, sizeof(wt));
/* close QSPI module */
deinit_qspi(current_spi_mode);
return (err);
} |
视频演示
心得体会
- 学会了如何生成三角波、正弦波;
- 模块化编程,有利于分解各个功能,减少串联出错的风险;
- 学习 QSPI-Flash 的读写操作;
|