3620|0

504

帖子

4

TA的资源

纯净的硅(高级)

楼主
 

[RTT&瑞萨超低功耗MCU RA2L1开发板]测评之SPI模块介绍驱动分析与测试 [复制链接]

SPI模块介绍

参考<<Renesas RA2L1 Group User’s Manual: Hardware>>的章节<<28. Serial Peripheral Interface (SPI) >>

 

特征

  • 2通道
  • 支持全双工和仅发送模式,支持3线制和4线制
  • RSPCK极性反转,相位翻转
  • MSB和LSB可设置
  • 位长可设置为  8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 32。
  • 32位收发缓存,支持双缓存。
  • 主模式下,RSPCK可以由PCLKB分频2~4096得到,从模式下RSPCK最大频率为PCLKB4分频。
  • 支持模式错误,校验错误,上下溢出错误检测
  • 支持四个片选SSL,单主模式 SSLn0~ SSLn3作为输出;多主模式 SSLn0输入,SSLn1~ SSLn3作为输出或者不用;从模式 SSLn0输入,SSLn1~ SSLn3不用。
  • SSL有效到RSPCK产生时钟输出可以配置 1 ~ 8 个RSPCK延迟;RSPCK停止产生时钟输出到SSL无效可以配置 1 ~ 8 个RSPCK延迟。
  • SSL无效到下次开始操作有效可以配置 1 ~ 8 个RSPCK延迟。
  • 可以直接控制SSL极性
  • burst可配置
  • 支持以下中断源,接收缓冲满,发送缓冲空,检测到错误,SPI空闲,发送完成。
  • 以上中断源支持连接到ELC模块
  • 支持配置回环模式
  • 支持输出引脚CMOS和开漏配置
  • 可以STOP模块支持降低功耗

模块框图如下

 

引脚

 

寄存器

这里列举下各寄存器的作用,大致了解下功能,实际编程时可以参考手册。

SPCR:控制寄存器,模块模式,中断使能,模块使能等配置。

SSLP:从模式SSL极性配置

SPPCR:引脚回环设置,MOSI空闲时电平设置

SPSR:状态寄存器,可以看手法缓冲区状态,空闲状态,错误状态

SPDR/SPDR_HA/SPDR_BY:数据寄存器,根据 SPDCR.SPBYT, SPDCR.SPBYT的设置可以按照32位,16位,8位方式访问。收发都是访问该寄存器。

 对于发送直接写寄存器就是写发送缓存

 

对于接收要设置SPDCR.SPRDTD用于选择读收还是法缓存

 

SPBR:波特率计算寄存器

计算公式为

 

其中PCLK为FPCLKB

n为该寄存器的值0~255

N为SPCMD0.BRDV[1:0]即分频值。

SPDCR:数据控制寄存器,设置 SPDR访问的方式,和读 SPDR时是读受还是发缓存。

SPCKD:SPCMD0.SCKDEN=1时设置 SSLni有效到RSPCK产生时钟延迟RSPCK时钟的个数,从模式要设置为0。

SSLND:SPCMDn .SLNDEN=1时设置RSPCK时钟停止到SSLni无效延迟RSPCK时钟的个数,从模式要设置为0。

SPND:SPCMD0.SPNDEN=1时设置SSLni无效到下一次有效延迟RSPCK时钟的个数,从模式要设置为0。

SPCR2:控制寄存器2,校验等设置

SPCMD0:SPI的模式,数据位数等设置。

 

操作

操作模式设置

支持一下模式

 

 

引脚设置

 PmnPFS.NCODR

 

 

MOSI在空闲时可以配置为以下状态

 

 

其他数据格式,SPI模式,错误检测中断等参考手册。

 

软件关键操作

以下是一些关键操作可以参考手册

 

PCLKB时钟

MSTPCRB使能SPI模块才能访问SPI寄存器,默认是不使能的。

 

设置中断

设置引脚

SPCR.SPE=0初始化

SPI各寄存器配置 如果使用DMADTC配置

使能SPI

中断处理

添加SPI驱动

安装RA Configuration

https://www2.renesas.cn/kr/en/software-tool/ra-smart-configurator#overview下下载,

 

 

或者wget https://github.com/renesas/fsp/releases/download/v4.0.0/setup_fsp_v4_0_0_rasc_v2022-07.exe

 

下载慢可以通过以下地址加速下载

https://gh.api.99988866.xyz/

 

下载后点击安装

 

 

 

 

 

 

 

添加SPI代码

 

 

 

 

SCI0和SPI0引脚重复,不使用SCI0,使用SPI0

 

 

添加如下SCI0的中断

 

名字分别为

spi_eri_isr,spi_rxi_isr,spi_tei_isr,sppi_txi_isr不添加idle中断

 

 

 

设置属性,如下

 

 

 

 

 

添加的代码如下

 

 

 

驱动代码位于

rt-thread\bsp\renesas\libraries\HAL_Drivers\drv_spi.c和drv_spi.h

添加到工程Drivers目录下。

 

rtconfig.h中添加

#define RT_USING_SPI

 

 

bsp_mcu_family_cfg.h注释掉#include "bsp_mcu_device_pn_cfg.h"

 

bsp_cfg.h中注释掉#include "board_cfg.h"

 

 

 

 

r_spi.c中实现了中断服务函数

void spi_rxi_isr(void);

void spi_txi_isr(void);

void spi_tei_isr(void);

void spi_eri_isr(void);

与vector_data.c和vector_data.h中对应。如果编译有错误可以看下这里名字是否对应。

 

drv_spi.c中spi0_callback改为spi_callback

 

编译还提示rt_spi_bus_attach_device找不到。

 

将\rt-thread\components\drivers\spi下的

spi_core.c

spi_dev.c

drv_spi.c添加到

工程Drivers下

 

配置

rtconfig.h中

添加宏

#define RT_USING_SPI

#define BSP_USING_SPI0

 

#define RT_USING_DEVICE_OPS 必须要否则

rt_spi_bus_device_init中

#ifdef RT_USING_DEVICE_OPS

    device->ops     = &spi_bus_ops;

#else

不会执行

 

rt_spi_configure时会调用device->bus->ops->configure(device, &device->config);空函数指针导致hardfault。

 

 

 

编译OK

 

运行输入list device就可以看到由spi0的设备了。

msh >list device

device           type         ref count

-------- -------------------- ----------

spi0     SPI Bus              0       

uart9    Character Device     2       

pin      Pin Device           0       

msh >

 

 

SPI驱动的使用

参考https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi

 

//需要包含spi.h

//drv_spi.h

 

挂载SPI设备

drv_spi.c中rt_hw_spi_device_attach调用rt_spi_bus_attach_device

 

配置SPI设备

rt_spi_configure

 

其他接口参考网页文档

函数

描述

rt_device_find()

根据 SPI 设备名称查找设备获取设备句柄

rt_spi_transfer_message()

自定义传输数据

rt_spi_transfer()

传输一次数据

rt_spi_send()

发送一次数据

rt_spi_recv()

接受一次数据

rt_spi_send_then_send()

连续两次发送

rt_spi_send_then_recv()

先发送后接收

 

SPI驱动分析

初始化过程

定义了RT_USING_SPI时

drv_spi.c中

INIT_BOARD_EXPORT(ra_hw_spi_init);

展开就是

__attribute__((used)) const init_fn_t __rt_init_ra_hw_spi_init __attribute__((section(x)))(.rti_fn.1) = fn

也就是定义了typedef int (*init_fn_t)(void);函数指针类型的变量

__rt_init_ra_hw_spi_init其值就是函数ra_hw_spi_init,变量放在了.rti_fn.1段。

 

类似的以下分别定义了连个变量

static int rti_board_start(void)

{

    return 0;

}

INIT_EXPORT(rti_board_start, "0.end");

 

static int rti_board_end(void)

{

    return 0;

}

INIT_EXPORT(rti_board_end, "1.end");

const init_fn_t __rt_init_rti_board_start __attribute__((section(.rti_fn.0.end))) = rti_board_start

const init_fn_t __rt_init_rti_board_end __attribute__((section(.rti_fn.1.end))) = rti_board_end

上面定义的__rt_init_ra_hw_spi_init位于段.rti_fn.1

所以位于段.rti_fn.0.end和段.rti_fn.1.end之间(按照字母顺序排列)。

 

rt_components_board_init函数中

    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)

    {

        (*fn_ptr)();

}

就是遍历上述定义的两个变量之间,从__rt_init_rti_board_start开始找到__rt_init_rti_board_end结束

中间就会找到__rt_init_ra_hw_spi_init调用其值指向的函数即调用ra_hw_spi_init

 

调用过程如下

rtthread_startup->

rt_hw_board_init->

rt_components_board_init->

ra_hw_spi_init->

rt_spi_bus_register->

 

互斥量

其中rt_spi_bus_register时会创建互斥量

rt_mutex_init(&(bus->lock), name, RT_IPC_FLAG_PRIO);

rt_spi_send_then_recv等接口中

rt_mutex_take会使用。

 

cs引脚

ra_hw_spi_configure时spi_dev->cs_pin = (rt_uint32_t)device->parent.user_data;会用到

所以rt_spi_bus_attach_device(&spi_dev,"spi00", "spi0", (void*)spi_cs);注意这里spi_cs前不要加&不是传其地址,而是就是传其值,只不过强制转换为(void*).

(rt_uint32_t)device->parent.user_data用的时候还是会强制转为(rt_uint32_t)。

 

顺便提一下pin驱动

drv_pgio.c中

const static struct rt_pin_ops _ra_pin_ops实现了io操作接口。

比如ra_pin_write

最终是调用R_BSP_PinWrite

通过bsp_io_port_pin_t可以看到

我们spi0使用的是p103所以对应的pin号是

BSP_IO_PORT_10_PIN_03 = 0x0A03,    ///< IO port 10 pin 3

 

 

spi底层配置

rt_spi_configure->

device->bus->ops->configure(device, &device->config);->

ra_hw_spi_configure->

R_SPI_Open

进行相应的底层初始化

 

其中ra_hw_spi_configure在

rt_err_t err = rt_spi_bus_register(&spi_config[spi_index].bus, spi_handle[spi_index].bus_name, &ra_spi_ops);时注册

static const struct rt_spi_ops ra_spi_ops =

{

    .configure = ra_hw_spi_configure,

    .xfer = ra_spixfer,

};

 

中注册的

此时可以看到spi0总线和spi00设备

msh >list device

device           type         ref count

-------- -------------------- ----------

spi00    SPI Device           0       

spi0     SPI Bus              0       

uart9    Character Device     2       

pin      Pin Device           0       

msh >

 

回环测试

将P100 J2-16和P101 J2-15短接在一起,也就是SPI0的MISO和MOSI短接在一起。

测试代码如下

hal_entry.c

/*

 * Copyright (c) 2006-2021, RT-Thread Development Team

 *

 * SPDX-License-Identifier: Apache-2.0

 *

 * Change Logs:

 * Date           Author        Notes

 * 2021-10-10     Sherman       first version

 */

#include <rtthread.h>

#include "hal_data.h"

#include <rtdevice.h>

#include <drv_spi.h>

 

#define LED1_PIN    "P502" /* Onboard LED pins */

#define USER_INPUT  "P004"

 

 

 

void hal_entry(void)

{

    rt_kprintf("\nHello RT-Thread!\n");

    rt_uint32_t led1_pin = rt_pin_get(LED1_PIN);

 

uint8_t sendbuf[6]={0,1,2,3,4,5};

  uint8_t readbuf[6]={0,0,0,0,0,0};

  

    rt_err_t res;

  static struct rt_spi_device spi_dev;

  static rt_uint32_t spi_cs = BSP_IO_PORT_10_PIN_03;

res = rt_spi_bus_attach_device(&spi_dev,"spi00", "spi0", (void*)spi_cs);

    if (res != RT_EOK)

    {

        rt_kprintf("rt_spi_bus_attach_device!\r\n");

    }

else

{

  rt_pin_mode(spi_cs, PIN_MODE_OUTPUT);   

rt_pin_write(spi_cs, PIN_HIGH);

 

        struct rt_spi_configuration cfg;

cfg.data_width = 8;

  cfg.max_hz = 12000000;

  cfg.mode = RT_SPI_MODE_1 | RT_SPI_MSB;

  spi_dev.bus->owner = &spi_dev;

  rt_spi_configure(&spi_dev,&cfg);

  rt_kprintf("spi0 send:\r\n");

for(int i=0;i<sizeof(sendbuf);i++)

{

rt_kprintf("%02x ",sendbuf);

}

rt_kprintf("\r\n");

rt_spi_transfer(&spi_dev, sendbuf, readbuf, sizeof(sendbuf));

rt_kprintf("spi0 rcv:\r\n");

for(int i=0;i<sizeof(readbuf);i++)

{

rt_kprintf("%02x ",readbuf);

}

rt_kprintf("\r\n");

}

 

    while (1)

    {

        rt_pin_write(led1_pin, PIN_HIGH);

        rt_thread_mdelay(500);

        rt_pin_write(led1_pin, PIN_LOW);

        rt_thread_mdelay(500);

    }

}

 

void irq_callback_test(void *args)

{

    rt_kprintf("\n IRQ03 triggered \n");

}

 

void icu_sample(void)

{

    /* init */

    rt_uint32_t pin = rt_pin_get(USER_INPUT);

    rt_kprintf("\n pin number : 0x%04X \n", pin);

    rt_err_t err = rt_pin_attach_irq(pin, PIN_IRQ_MODE_RISING, irq_callback_test, RT_NULL);

    if (RT_EOK != err)

    {

        rt_kprintf("\n attach irq failed. \n");

    }

    err = rt_pin_irq_enable(pin, PIN_IRQ_ENABLE);

    if (RT_EOK != err)

    {

        rt_kprintf("\n enable irq failed. \n");

    }

}

MSH_CMD_EXPORT(icu_sample, icu sample);

 

看到收到的和发送的一致说明回环测试OK,该测试不能测试CLK和CS,CLK和CS可以使用逻辑分析监控。

 

 

 

总结

rt-thread的spi设备驱动比较绕,对于新手来说会遇到很多坑,任何一个坑都可能导致异常,这是一个非常痛苦的过程,个人认为搞得太复杂了。

官方的文档

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi?id=spi-%e8%ae%be%e5%a4%87%e4%bd%bf%e7%94%a8%e7%a4%ba%e4%be%8b

只是单纯的描述各个API接口,并没有解释说明,spi驱动的架构,怎么初始化,设备怎么注册,底层到底是怎么配置的,cs引脚到底是怎么关联的等等,文档有待加强。对于一个初使用该系统环境的开发人员来说十分不友好。

 

另外驱动太绕,各种指针回调,反复绕,实际上是可以简化的,没必要搞得这么复杂,对于嵌入式简单即是美。

另外一方面驱动并没有完全抽象出来,理论上驱动应该是完全可移植的,底层只依赖一组最小的HAL操作接口,比如SPI的send,read,收发中断回调,参数配置等这几个接口,驱动层只应该调用这几个接口,这几个接口不同芯片不同实现即可。而不是整个drv_xxx.c都要根据不同芯片改写。drv_xxx.c应该是完全复用的,其实现缓冲取,消息对列等操作应该是完全抽象可移植的。而现在的做法是每个芯片都实现一套bsp,每套bsp里都要写drv文件,工作量太大了,没有分好层次,严格界定驱动和HAL的界限,反而是搞了一套设备的注册接口,各种回调,指针,搞得很复杂,有点注重形式轻内在了。现在这种做法是方便编译构建,但是没有真正的分好驱动层的架构,使用容易出错且复杂不好调试。

 

 

 

 

点赞 关注

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/6 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表