【每周分享】以psram为例测一测瑞萨RA2E1的SPI功能与性能

RA2E1基于Arm Cortex-M23,频率最高48MHZ,FLASH最大能到128kB,SRAM最大有16kB。这款产品采用优化的制程和瑞萨电子的低功耗工艺技术,超低功耗表现很牛。RA2E1支持1.6V到5.5V宽电压,很多情况下可以省去电平转换芯片,节省BOM,很赞的一个特性。总的来说RA2E1比较适合电池供电需要低功耗的
系统,比如耳温枪、遥控器、电子秤、血糖仪、智能台灯等等。笔者这块RA-Eco-RA2E1开发板由RA生态工作室出品(话说他家出了好多好多基于RA芯片的开发板,笔者看得口水直流,看看这篇文章能不能把他家吸引来二姨家开个版面放开测评机会

),基于RA2E1家族的R7FA2E1A7,64KB Flash,16KB SRAM,引出了全部引脚,适合开发阶段使用。开发板未板载调试器(其实笔者一直觉得板载调试器是比较浪费的,不如板载spi nor flash、can收发器等更为合算,又可充分利用资源),虽然瑞萨有串口烧录工具,但为方便开发调试,最好准备一个daplink兼容的调试器,市面上很多选择。

话说这是笔者第一次使用基于Cortex-M23的开发板,板子到手后把玩了好多天,对RA2E1的SPI比较感兴趣,以前也没用过RA系的SPI,现在想体验一把并做个性能测试。


RA2E1 SPI介绍
RA2E1其实有两个SPI控制器:由SCI可以转成简易SPI控制器,另一个是原生的SPI控制器。本文重点是后者。此SPI控制器一些关键特性如下:
支持Master和Slave模式;
4Byte的收/发FIFO;
SCLK极性和相位均可调;
支持MSB或LSB,传输比特长度可支持8、9、10、11、12、13、14、15、16、20、24、32bit,注这里不>是传输字节长度,而是bit长度,8bit最常见,但有些SPI设备bit长不是8bit的,可能是9bit、10bit等。一次SPI传输的数据是不限的,可改变,是传输bit长的倍数;
最重要的一个等式是关于传输比特率的:

复制
Bit Rate = f(PCLKB)/(2(n+1)2^N)


其中n是SPBR的“n”,N是BRDV的“N”,详见官方数据手册


RA系spi api简介

复制
R_SPI_Open()


此函数用来打开并初始化SPI控制器,参数由RASC配置好,RASC使用见下文。
 

复制
R_SPI_Write()


此函数用来发起SPI传输向slave设备写入。第二个参数指定传输buf,第四个参数是上文提到的传输bit长,第三个参数意思是传输多少个bit长。
 

复制
R_SPI_Read()


此函数用来发起SPI传输从slave读取。参数意义和R_SPI_Write()一致
 

复制
R_SPI_Close()


此函数用来关闭SPI控制器并disable相应中断

由此可见关于SPI API的使用还是比较简单的,厂商都封装好了。


测试用psram介绍
笔者这款psram模块基于ESP-PSRAM64H,3.3V电压,8MB容量,最高频率可达133MHZ,支持标准SPI模式和Quad SPI模式。

RASC中SPI相关配置
RASC全称Renesas Advanced Smart Configurator,是瑞萨用于配置他自家芯片的pll/clk,pinmux、中断等的工具,会用CubeMX就会用RASC。在RA2E1 SPI配置时,主要注意三点:
使能SPI pin

 



使能一个gpio
此gpio控制用于psram的片选信号,据psram手册,片选需要程序控制。笔者选p500,这根引脚在板子排针上和p100等靠得很近,方便接杜邦线。
 



stacks栏配置spi
特别注意回调函数名称,笔者用的名字是spi_callback
 



SEGGER RTT的使用
因为spi接psram模块本来就需要MOSI、MISO、CLK三根线,再考虑到vcc、gnd、片选,总共要6根线,如果用串口打印的话需要再接3根线,接线能做但挺繁的,所以这时候SEGGER_RTT就派上用场了,只要接上SWD相应引脚即可烧录调试又能获得RTT打印支持。SEGGER RTT使用过程非本文重点,步骤在此略过,其实挺简单的。


计时的考虑
因为要测试性能,所以需要计时。但CM23无DWT,不得已只能上systick了,因systick只有24位,48MHZ时钟下0.3s多一点也就需要reload了,所以这里计时需要考虑在SysTick_Handler()里做个计数,综合算时间。笔者做法如下:
在初始化过程中以0.3s的中断间隔启动systick:

复制
SysTick_Config(14400000); // 0.3s per interrupt @48MHZ



在SysTick_Handler()中,ticks加一计数:

复制
static unsigned int ticks;



void SysTick_Handler(void)

{

        ++ticks;

}



获得us级时间的函数:

复制
static uint32_t get_us()

{

        return ticks * 300000 + (0xffffffu - SysTick->VAL) / 48;

}



SPI回调函数实现

复制
static volatile bool g_transfer_complete;



void spi_callback(spi_callback_args_t *p_args)

{

        if (SPI_EVENT_TRANSFER_COMPLETE == p_args->event)

                g_transfer_complete = true;

}

瑞萨家的固件库中像串口、SPI、I2C等器件的API基本都是非阻塞的,是基于中断事件的,所以要想知道传输是否完成需要等待中断事件,OS环境中可由os原语实现,裸机环境就得poll一个标志位了,然后由回调函数调用os原语(对应OS环境)或设置标志位(对应裸机环境)即可。`

实现psram初始化
根据ESP-PSRAM64H的datasheet,它上电后自动初始化进入standby模式,不过笔者初始化后还是向PSRAM发送了RESET_EN和RESET命令,发命令的函数如下所示:
 

复制
#define CMD_WRITE       0x02

#define CMD_READ        0x03

#define CMD_FAST_READ   0x0b

#define CMD_RESET_EN    0x66

#define CMD_RESET       0x99

#define CMD_READ_ID     0x9f



static int psram_send_cmd(const uint8_t cmd)

{

        fsp_err_t err;



        g_transfer_complete = false;

        err = R_SPI_Write(&g_spi0_ctrl, &cmd, 1, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_read_id write cmd failed\n");

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        return 0;

}



读取psram id

复制
static int psram_read_id(uint8_t *buf)

{

        uint8_t cmd[4];

        fsp_err_t err;



        cmd[0] = CMD_READ_ID;

        /* cmd[1], cmd[2] and cmd[3] are dummy address */



        g_transfer_complete = false;

        err = R_SPI_Write(&g_spi0_ctrl, cmd, 4, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_read_id write cmd failed\n");

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        g_transfer_complete = false;

        err = R_SPI_Read(&g_spi0_ctrl, buf, 6, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_read_id read ID failed\n");

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        return 0;

}





实现psram的读取

复制
int psram_read(uint32_t addr, void *buf, int len)

{

        uint8_t cmd[5];

        fsp_err_t err;



        cmd[0] = CMD_FAST_READ;

        cmd[1] = (uint8_t)(addr >> 16);

        cmd[2] = (uint8_t)(addr >> 8);

        cmd[3] = (uint8_t)(addr);

        /* cmd[4] is dummy */



        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_LOW);



        g_transfer_complete = false;

        err = R_SPI_Write(&g_spi0_ctrl, cmd, 5, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_read write cmd failed\n");

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        g_transfer_complete = false;

        err = R_SPI_Read(&g_spi0_ctrl, buf, len, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_read read data failed %lx %d\n", addr, len);

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);



        return len;

}





实现psram的写入

复制
int psram_write(uint32_t addr, void *buf, int len)

{

        uint8_t cmd[4];

        fsp_err_t err;



        cmd[0] = CMD_WRITE;

        cmd[1] = (uint8_t)(addr >> 16);

        cmd[2] = (uint8_t)(addr >> 8);

        cmd[3] = (uint8_t)(addr);



        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_LOW);



        g_transfer_complete = false;

        err = R_SPI_Write(&g_spi0_ctrl, cmd, 4, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_write write cmd failed\n");

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        g_transfer_complete = false;

        err = R_SPI_Write(&g_spi0_ctrl, buf, len, SPI_BIT_WIDTH_8_BITS);

        if (FSP_SUCCESS != err) {

                SEGGER_RTT_printf(0, "psram_write write data failed %lx %d\n", addr, len);

                R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);

                return -1;

        }

        /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */

        while (g_transfer_complete == false) {;}



        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);



        return len;

}





测试代码

复制
void psram_test(void)

{

        unsigned int i;

        uint32_t addr;

        uint32_t t1;

        uint8_t testbuf[64];



        psram_init();



        memset(testbuf, 0x5a, sizeof(testbuf));

        t1 = get_us();

        for (i = 0; i < 10000; ++i) {

                psram_write(i * 64, &testbuf, 64);

        }

        t1 = get_us() - t1;

        t1 /= 1000;

        SEGGER_RTT_printf(0, "PSRAM write speed: %lld B/s.\n", 64 * 10000 * 1000 / t1);



        t1 = get_us();

        for (i = 0; i < 10000; ++i) {

                psram_read(i * 64, &testbuf, 64);

        }

        t1 = get_us() - t1;

        t1 /= 1000;

        SEGGER_RTT_printf(0, "PSRAM read speed: %lld B/s.\n", 64 * 10000 * 1000 / t1);



        SEGGER_RTT_printf(0, "PSRAM test done\n");

}



编译

复制
cmake -B /tmp/build

cmake --build /tmp/build -j8

在/tmp/build目录下有ra2e1.elf和ra2e1.srec文件生成

烧录

复制
pyocd load -e sector -t R7FA2E1A7 /tmp/build/ra2e1.elf




pyocd获取rtt打印信息

复制
pyocd rtt -t r7fa2e1a7 -a 0x20004a04


其中0x20004a04是_SEGGER_RTT符号地址,在编译结果的ra2e1.map中搜索即可获得


初步数据解析
烧录后板子重启,运行截图如下:
 


由此可见,写入速度是549828 Byte/s,读取速度是545144 Byte/s。因PCLKB是24MHZ,那么理论上最大Bit Rate按上文公式可达12Mbps,所以SPI总线理论速度传输速度大概能到1.5MB/s,实际速度大概只有理论速度的三分之一。下文做一些实验尝试提升SPI传输速度。


尝试提升PCLKB的频率
按官方手册,PCLKB的频率最高可到32MHZ,咱们在RASC配置试试。为了让PCLKB达32MHZ,可让HOCO频率升到64MHZ,然后2分频即可。但此时cpu最高只能48MHZ,所以只能两分频给ICLK,故cpu频率此时到32MHZ。编译烧录测试截图如下:

 


由此可见,读写速度反而下降了,猜测SPI传输瓶颈不在SPI总线上而在CPU上(毕竟现在的测试代码中是由CPU搬运数据),可能是CPU(只有32MHZ)来不及搬运导致。

尝试关闭param_checking
这个选项在RASC中可配置,测试结果基本不变。

尝试开启rxi_transmit
这个选项同样在RASC中可配置,测试截图如下:

 


由此可见在未使用dma引擎时rxi_transmit可提升SPI传输速度大概1个百分点。

思考与总结
其实如果由DMA引擎来搬运SPI数据,效率最高、速度最快。笔者搜索官方文档发现RA2E1集成了一个名叫DTC(data transfer controller)模块,但目前不太清楚这个DTC是否可以用来搬运SPI数据。所以如果DTC可用于搬运SPI数据,建议官方增加这样使用样例代码。的
---------------------
作者:xhackerustc
链接:https://bbs.21ic.com/icview-3437356-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值