RT-Thread SPI作为从模式接收数据的使用方法

电子说

1.3w人已加入

描述

最近遇到了如下需求:

MCU作为主控芯片通过SPI与蓝牙芯片连接。
蓝牙芯片会时不时向MCU发送大量定长的数据包。
这种情况下,如果MCU的SPI接口采用主模式,通过查询的方式询问蓝牙芯片是否有数据要发送,就会非常占用资源,并且遇到突发大量数据也可能会来不及处理。

比较好的一种方法是,MCU采用从模式的SPI。蓝牙芯片无脑向MCU吐数据。如果主控MCU的SPI时钟最大频率大于蓝牙芯片的SPI最大频率,此方法可以跑到蓝牙芯片SPI的传输极限。

大体思路:

初始化SPI为从模式,并为SPI_CS引脚注册中断函数,下降沿触发
在中断函数中,启动SPI的接收。
SPI接收完成后,做其他处理,比如解析,转发等

代码实现

下面是如何实现,平台采用了STM32F1系列芯片,启用SPI DMA传输,RT-Thread 4.0.2,SPI约定为Slave,MODE3,MSB,CS active low。一次传输长度为package_length。

使用内存池+邮箱的缓冲方式,当然也可以使用消息队列,根据自己的喜好。此处对中断做了底半处理。

初始化
SPI初始化
static struct rt_spi_device spi_device; //
static struct stm32_hw_spi_cs spi_cs; //中断引脚
static int spi_init(void)
{
rt_pin_mode(CS_PIN, PIN_MODE_INPUT_PULLUP);
/
attach the device to spi bus
/
spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
RT_ASSERT(uwb_device != RT_NULL);
spi_cs = (struct stm32_hw_spi_cs )rt_malloc(sizeof(struct stm32_hw_spi_cs));
RT_ASSERT(uwb_spi_cs != RT_NULL);
spi_cs->GPIOx = GPIOA;
spi_cs->GPIO_Pin = 4;
result = rt_spi_bus_attach_device(uwb_device, "spi10", "spi1", (void )uwb_spi_cs);
if (result != RT_EOK)
{
LOG_E("%s attach to %s faild, %dn", "spi10", "spi1", result);
return result;
}
LOG_D("%s attach to %s done", UWB_SPI_NAME, UWB_SPI_BUS);
/
get SPI bus /
spi_device->bus->owner = spi_device;
/
configure SPI device
/
{
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_SLAVE | RT_SPI_MODE_3 | RT_SPI_MSB;
cfg.max_hz = 8 * 1000 * 1000;
rt_spi_configure(spi_device, &cfg);
}
if (rt_device_open((rt_device_t)spi_device, RT_DEVICE_FLAG_DMA_RX) != RT_EOK)
{
LOG_E("open UWB SPI device %s error.", "spi10");
return -RT_ERROR;
}
return RT_OK;
}
!!!注意,这里需要修改一下rt_spi_configure函数中的宏定义RT_SPI_MODE_MASK,从

(RT_SPI_CPHA | RT_SPI_CPOL | RT_SPI_MSB)
改为

(RT_SPI_SLAVE | RT_SPI_CPHA | RT_SPI_CPOL | RT_SPI_MSB)

否则无法将SPI接口配置为从模式,调用rt_spi_revice_message会崩溃。

初始化信号量,邮箱和内存池
/* create RX semaphore /
spi_start_sem = rt_sem_create("spi1_start", 0, RT_IPC_FLAG_FIFO);
/
create RX mp /
spi_mp = rt_mp_create("spi_mp", SPI_MB_LEN, RT_ALIGN(sizeof(rt_uint8_t), sizeof(intptr_t)) * package_length);
/
create RX mailbox /
rt_mb_init(&spi_mb, "UWB_mb", &spi_mb_pool[0], sizeof(spi_mb_pool) / 4, RT_IPC_FLAG_FIFO);
为CS引脚绑定中断函数
rt_pin_attach_irq(4, PIN_IRQ_MODE_FALLING, (void (
)(void *))spi_cs_isr, RT_NULL);

此时,可以先不使能中断,可以等待系统所有初始工作完成后,由其他线程使能中断,以启动SPI接收。

到此,初始化的工作就完成了。接下来,要进行数据的接收工作,为此我们需要创建一些其他的函数。

数据的接收

首先我们需要创建一个中断函数,这个中断函数通过CS引脚的下降沿触发,用来通知系统开始接收数据。

static void spi_cs_isr(void)
{
/* enter interrupt /
rt_interrupt_enter();
rt_sem_release(spi_start_sem);
/
leave interrupt */
rt_interrupt_leave();
}

然后,我们还需要:一个线程用来启动DMA接收;一个中断函数用于通知系统DMA接收已经完成。

使用DMA方式的好处是,全部的SPI接收过程可以交给DMA。这种非阻塞的方式使得,系统在这个时候可以搞搞其他事情(相当于双线程???)。在SPI大量传输数据时尤其好用。

static uint8_t *rx_buff = RT_NULL;
static void spi_start_thread_entry(void *parameter)
{
struct rt_spi_message spi_msg;
spi_msg.send_buf = RT_NULL;
spi_msg.length = uwb_package_length;
spi_msg.cs_take = 0;
spi_msg.cs_take = 0;
spi_msg.next = RT_NULL;
while (1)
{
if (rt_sem_take(spi_start_sem, RT_WAITING_FOREVER) == RT_EOK)
{
rx_buff = rt_mp_alloc(spi_mp, RT_WAITING_NO);
if (rx_buff != RT_NULL)
{
rt_spi_revice_message(spi_device, &spi_msg);
}
}
}
}

这里使用了RT_WAITING_NO的方式来申请空间,如果没有申请到(缓冲区已满),就抛弃这条数据。

使用rt_spi_revice_message函数来启动DMA接收,并且约定了接收的长度固定为package_length。

DMA接收完成函数

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
if (rx_buff != RT_NULL)
{
rt_mb_send(&spi_mb, (rt_uint32_t)rx_buff); //发送邮件
}
}
}

最后,还需要一个线程用于处理接收到的数据

static void spi_dma_clp_thread_entry(void *parameter)
{
rt_uint8_t *net_tx_buff = RT_NULL;
while (1)
{
if (rt_mb_recv(&uwb_mb, (rt_ubase_t *)&net_tx_buff, RT_WAITING_FOREVER) == RT_EOK)
{
//TODO
//data process
}
rt_mp_free(net_tx_buff);
net_tx_buff = RT_NULL;
}
}

到此,基于RT-Thread的SPI从接收就基本完成了。这些只是一个大体的思路,也可以使用自己喜欢的方式,或者添加其他的功能。如果大家有更好的思路,欢迎分享出来。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分