SPI

背景

随着科技的不断发展, LCD 显示技术已经成为市场主流。LCD显示能够提供均匀的、流畅的、色彩鲜艳的动态或静态的图像,尤其在家电应用、智能家居应用、消费电子等产品中,受到了广大消费者的青睐,同时也受到了市场的广泛关注,为此,灵动微电子推出了搭载MM32系列MCU的SPI LCD彩屏参考方案。

硬件方案介绍

LCD显示应用开发一直以来都是方案开发公司的难点项目,在开发过程中的图片转码、压缩、布局和存储等开发过程都需要花费大量的时间成本,同时个性化的创意显示方案和炫酷的显示效果对硬件的要求越来越高,屏幕刷新速率和硬件成本之间难以平衡。灵动微电子推出的SPI LCD显示参考方案,通过SPI接口读取外部SPI FLASH存储芯片中存储的字体、图片等数据资源,再通过另外一路SPI驱动SPI LCD彩屏显示。对于不同尺寸、不同分辨率的 SPI LCD 彩屏,客户可以选择灵动微电子不同规格的MCU来实现刷屏效果,硬件设计框图如下所示:

1.png

灵动微电子推出的SPI LCD彩屏参考方案,可以为用户提供MM32F0140、MM32F0160、MM32F5230、MM32F5330等多个系列MCU作为主控,可以满足不同应用场景、功耗及成本需求。

2.png

MM32F0140系列:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_value_line/mm32f0140/ 

3.jpg

MM32F0160系列:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_value_line/mm32f0160/ 

4.jpg

MM32F5230系列:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_performance/mm32f5230/ 

5.jpg

MM32F5330系列:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_performance/mm32f5330/ 

6.jpg

灵动微电子基于MM32F0140、MM32F0160、MM32F5230、MM32F5330 等系列MCU,已经完成了SPI LCD彩屏的完整显示方案,并能够提供硬件参考、软件参考设计和开发工具等,可以帮助客户快速的完成产品设计,提供更加高效、稳定的解决方案。

软件方案介绍

灵动微电子提供的SPI LCD彩屏参考方案,包括MCU的数据手册、用户手册、开发板、参考原理图、驱动库及例程、彩屏驱动程序、示例代码、软件工具使用说明及完整的GUI等,可以帮助客户快速应用到实际项目中去,加快产品的研发进度,能将产品快速地推向终端市场。

SPI LCD彩屏参考方案的软件部分包含了SPI FLASH驱动及读写操作、SPI LCD彩屏驱动及显示,GUI API函数等。通过驱动分层架构设计,方便客户移植,能够快速添加LCD软件部分到实际项目代码中。

7.png

灵动微电子基于MM32F0140、MM32F0160、MM32F5330等系列MCU已经完成了针对不同尺寸、不同分辨率、不同显示驱动的SPI LCD彩屏的完整软件设计,可以帮助客户快速的完成硬件方案验证、显示效果调试、显示驱动移植等工作。

方案性能及效果

灵动微电子推出的部分系列SPI LCD彩屏参考方案,使用MM32 MCU特殊的SPI控制技术,结合软件处理算法,能够实现高效的刷屏技术,MCU资源和彩屏全屏刷新性能如下表所示:

8.png

0.96寸(80x160)实际显示效果展示:

9.png

1.47寸(172x320)实际显示效果展示:

10.png

方案优势

灵动微电子的SPI LCD彩屏参考方案,使用灵动微电子MCU作为主控,通过SPI接口读取外挂SPI FLASH存储芯片中的字体、图片等数据资源,然后再通过SPI接口将显示数据传输到SPI LCD彩屏上进行显示,此方案有如下优势:

  • 多规格MCU供选择,满足客户不同方案需求。

  • 使用MM32 MCU特殊的SPI控制技术,结合软件处理算法,1个SPI接口的MCU也能实现高效刷屏效果。

  • 完整的软硬件参考方案,加快客户产品研发。

  • 控制简单,外围电路易实现。

  • 支持所有3线/4线SPI接口的LCD彩屏,客户可根据LCD彩屏的驱动、显示分辨率选择适合的MCU。

  • 针对不同应用场景,能够根据其他功能资源需求进行平台化MCU选型,方便客户定制化需求方案的开发。

总结

灵动微电子提供的SPI LCD彩屏参考方案,基于MM32 MCU特殊的SPI控制技术,结合软件处理算法,实现方式灵活,外围电路易实现,控制简单,非常利于自动化生产测试,可以大大的缩短了产品样机调试的周期。同时,借助一些辅助系统调试的功能,能为客户带来全方位、高性能的LCD显示屏背光驱动方案。此外,灵动微电子可以提供全方位的线上及线下支持,帮助客户快速的完成产品研发,助力客户产品量产,快速上市。有关资料申请事宜,请和灵动微电子的销售同事接洽。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 23

01、问题描述

客户在项目开发中使用STM32F427GT6的SPI连接外部Flash时,发现在常温下能正常读写,但是在高温下一段时间后(大概5分钟左右)出现读写异常的情况。读写异常时发生在发送0x5指令后,返回数据通过软件读取的是0,而硬件抓取的是1。同时也发现同一份代码,同样硬件,如果flash换成别的厂家的,在同样温度条件下又没有出现读写异常。

02、问题的排查

根据客户的描述,初期怀疑是否是不同Flash厂家的兼容性问题,现场进一步测试,发现客户软件在70℃环境温度下,除了program、erase时寄存器会读错数据,用只读指令0x03也会读错数据(0x55、0xaa会被软件读成0x54、0xab)。

根据这个结果,我们怀疑到tCLQV这个参数。看上去当前的软件是在flash输出数据时,在CLK下降沿时去采集flash MO数据的,所以高温引起的细微的tCLQV变化可能会导致软件采集出错。我们建议MCU在下一个CLK的上升沿去采集数据,此时flash MO数据已经稳定为1。

1.png

现场调整GPIO(即flash CLK/SI/SO)OSPEEDR速率后异常现象消失,GPIO速率调整后CLK信号斜率变大,tCLQV跟随变小,软件抓到错误数据的现象消失,这个实验结果也与上述tCLQV这个怀疑点相匹配。下面是不同GPIO速率下的测试结果。

GPIO_SPEED_FREQ_LOW,常温:tCLQV=5.584ns。

GPIO_SPEED_FREQ_LOW,70℃:tCLQV=6.064ns,FAIL。

GPIO_SPEED_FREQ_MEDIUM,70℃:tCLQV=4.805ns。

GPIO_SPEED_FREQ_HIGH,70℃:tCLQV=4.577ns。

03、原因的进一步分析

进一步了解客户系统的初始化,其中clock配置信息如下:采用外部晶振为25MHZ,plln=360,pllm=25,pllp=2,pllq=8,系统主频:25/25*360/2=180MHz,APB2:180/2=90MHz,SPI的波特率为2.8MHz。SPI的引脚设置均为GPIO_Initure.Speed为low。

查找到STM32F42xx的勘误手册,我们发现有同样问题的描述:

2.png

对于文档推荐的2种workaround也和我们测试时发现的一样。

3.png

至此也是能较好的和客户解释了MCU底层的一些原理,并建议客户按照相应workaround的配置,去设定APB总线与OSPEEDR的关系,最终让问题得以解决。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 282

01、引言

在STM32的应用中,SPI算是用的比较多的外设了,也是单片机最常见外设之一。客户说它执行了关闭SPI的代码,竟然会导致Flash中的WRPERR标志置位,致使应用碰到一些问题。这就奇怪了,SPI和内部Flash看起来是风马牛不相及的事情,为什么会发生这种事呢?一起来看看吧。

02、问题

2.1 问题起源

客户在使用STM32L072RBT6的时候,使用STM32 CubeL0库,在程序编写时,发现执行关闭SPI代码时,会导致Flash的写保护错误标志WRPERR置位,导致其后面准备写EEPROM的时候,就无法对EEPROM写入了。

客户使用两个标志flag1和flag2,来观察WRPERR标志的变化。代码如图1所示。

1.png

图1.用户测试代码

在执行这个代码时,前面flag1还等于0,执行到flag2那句,就变成flag2等于1了,同样地取了WRPERR标志位的值。所以客户就怀疑执行_HAL_SPI_DISABLE()会把Flash的WRPERR标志置1了。

因为在对EEPROM编程中,需要先调用位于stm32l0xx_hal_flash.c中的FLASH_WaitForLastOperation()函数,此函数中,将会对Flash所有错误标志进行检查,如果出现了错误,它则返回HAL_ERROR,导致后续对EEPROM的编程不会被执行。

2.2 问题重现

使用NUCLEO-L053R8来验证客户的这个问题。在\STM32Cube_FW_L0_V1.10.0\Projects\STM32L052R8-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling例程中直接进行修改测试。

首先,把客户的测试代码加到例程中SPI初始化之后的位置。如图2所示。

2.png

图2.测试代码1(位于SPI初始化之后)

编译,并在线调试,发现并没有出现客户所描述的问题。如图3所示。

3.png

图3.测试代码1结果(位于SPI初始化之后)

可以看到,WRPERR的值并没有被置1,Flag1和Flag2的值也都是0。那么,为什么客户说他那边会有这个问题呢?

再回头仔细看一下客户的测试代码,发现客户的测试代码中并没有对SPI进行初始化,其_HAL_SPI_DISABLE()代码是放在其他外设初始化之后的。

好,那么再来修改一下测试代码,把客户这三句测试代码挪动到SPI初始化之前,如图4所示。

4.png

图4.测试代码2(位于SPI初始化之前)

编译,并在线调试,这时,会惊奇地发现客户所描述地问题来了。其结果如图5所示。

5.png

图5.测试代码2结果(位于SPI初始化之前)

可以看到,这时Flash的WRPERR标志位置1了,测试代码中,flag2的值也跟flag1不同了。

再做一个实验,将此处的HAL库写法,改成直接操作寄存器,来试一下。测试代码变成是图6这样的。

6.png

图6.测试代码3(位于SPI初始化之前,直接操作寄存器)

编译,在线调试,这次又惊喜地发现,问题不见了。结果如图7所示。

7.png

图7.测试代码3结果(位于SPI初始化之前,直接操作寄存器)

三种操作,为什么只有第二种方式有问题呢?而且为什么错的偏偏是Flash的写保护错误标志WRPERR呢?接下来可以分析一下它们的反汇编代码,看看到底是哪里出问题了。

2.3 反汇编分析

对于三种情况,把反汇编拉出来看最清楚其操作过程了。

先分析第一种情况——测试代码位于SPI初始化之后。其反汇编如图8所示。

8.png

图8.测试代码1的反汇编(位于SPI初始化之后)

从之前的Watch窗口,知道flag1的地址为 0x2000000c,flag2的地址为0x2000000d。

现在对三句C语言测试语句的反汇编语句进行解析,如下:

9.png

10.png

可以看到,这段汇编是一点问题都没有的。

接下来,先分析第三种情况——也就是测试代码放在SPI初始化之前,但是使用直接操作寄存器的方式。其反汇编如图9所示。

11.png

图9.测试代码3的反汇编(位于SPI初始化之前,直接操作寄存器)

从之前的Watch窗口,知道flag1的地址为0x2000000c,flag2的地址为0x2000000d。

现在对三句C语言测试语句的反汇编语句进行解析,如下:

12.png

13.png

可以看到,这段汇编也是一点问题都没有的。

最后,再来分析一下有问题的第二种情况,也就是测试代码放在SPI初始化之前,但是使用_HAL_SPI_DISABLE()关闭SPI的情况。其反汇编如图10所示。

14.png

图10.测试代码2的反汇编(位于SPI初始化之前)

从之前的Watch窗口,知道flag1的地址为0x20000008,flag2的地址为0x20000009。

现在对三句C语言测试语句的反汇编语句进行解析,如下:

15.png

16.png

可以看到,问题出在哪了?问题就出在“STR R3,[R 2]”这个语句上,这个语句在0x00000000这个位置写值,而0x00000000此时映射的是Flash的地址0x08000000,也就是Stack Pointer的位置。如图11和图12所示。

17.png

图11.0x00000000地址的数据

18.png

图12.0x08000000地址的数据

首先,这个位置本来就不应该被修改。

第二,因为没有对Flash程序存储器进行解锁,就往里边写值,就会造成写保护错误,导致WRPERR标志位置位。所以,可以明白为什么WRPERR会被置位了。

可是关键的问题在哪儿呢?在执行“LDR  R2,[R0,#4]”这条语句时,R2本来应该是SPI2_CR1的地址,但是它竟然是0x00000000!如图13所示。

19.png

图13.0x2000000c地址的数据

从Watch窗口来看一下SpiHandle的情况。如图14所示。

20.png

图14.SpiHandle(未初始化)

从图14可以看到,其实刚才的0x2000000c地址就是SpiHandle结构体的地址,也是SpiHandle.Instance的地址,而SpiHandle.Instance的值为0。SpiHandle.Ins tance.CR1的地址为0x0,导致显示它装载的值是Stack pointer的值0x20000468,这里本应该是SPI2_CR1的地址和SPI2_CR1的值。

也就是因为这里的问题,才会导致了后面的WRPERR错误。

2.4 代码分析

再回到代码这边来看一下,有问题的代码究竟是有什么情况。客户的代码主要就是一句关闭SPI的语句“_HAL_SPI_DISABLE(&SpiHandle);”。

这个语句是怎么解析的?它再stm32l0xx_hal_spi.h中解析,如图15所示。

21.png

图15._HAL_SPI_DISABLE函数

看到这个函数时,看到了重要的字眼——“Instance”!就明白是什么问题了,因为这个SpiHandle.Instance还没有被初始化呢!这也说明了为什么在图14中,看到的SpiHandle.Instance的值为0x0,而SpiHandle.Instance. CR2的值为0x20000468。关键就在于这个SpiHandle. Instance还没有初始化。

所以,把客户的测试代码放在SPI初始化代码之后没有问题,就是因为这个SpiHandle.Instance已经被初始化过了。所以,它不会有问题。

03、问题解决

本来客户的代码就没有必要这么写,因为SPI都没初始化,对它进行关闭并没有什么意义。

如果非要在这里关闭SPI的话,那就要先对SpiHandle.Instance进行初始化才行。如图16所示。

22.png

图16._HAL_SPI_DISABLE函数

加了“SpiHandle.Instance=SPIx;”初始化后,再跑这段代码,就不会出现客户所说的问题了。

现在再来看一下SpiHandle的情况。

23.png

图17.SpiHandle(SpiHandle.Instance已初始化)

经过对SpiHandle.Instance的初始化,这里就可以看到SpiHandle.Instance的值为0x40003800了,为SPI2外设寄存器的基地址,而且可以看到SpiHandle.Instance. CR1的地址就是SPI2_CR1的地址0x40003800,值也是SPI2_CR1的值0x0了。

04、小结

在用户代码中,SpiHandle只是定义了SPI_HandleTypeDef结构体,其各种参数并还没有进行实际初始化。在没有初始化的前提下,对其进行操作时不对的,也是危险的,应该在写代码的时候引起重视。

使用HAL库的时候,如果要对一个外设进行任何的操作,请务必记得它是被初始化过的。否则,出了问题可能都不一定知道。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

引言

MindSDK为MM32主流的微控制器,实现了一系列SPI驱动的样例工程。本文通过讲解 SPI 模块的样例工程,介绍 SPI 模块的功能和用法。关于 SPI 模块对应的驱动程序,以及 SPI 外设模块硬件的实现细节,可具体查阅 MindSDK 工程的源文件,以及 MM32 微控制器(例如MM32F5270)的用户手册。

样例工程

MindSDK 中为 SPI 驱动设计的样例工程包括:

  • spi_master_basic

  • spi_master_tx_dma

  • spi_master_xfer_dma

  • spi_master_b2b_interrupt

  • spi_master_b2b_polling

  • spi_slave_b2b_interrupt

其中,spi_master_basic、spi_master_tx_dma 与 spi_master_xfer_dma 分别演示了 SPI 做主机的典型数据传输方式,包括:基础数据传输、SPI 使用 DMA 发送数据、SPI 使用 DMA 收发数据。另外,还有一些 SPI 板级通信的样例,通过将一块 SPI 做主机,另一块 SPI 做从机的方式进行一系列板对板的基础数据传输,主机样例与从机样例结合使用,包括:SPI做主机进行中断数据传输,SPI做主机进行轮询数据传输,SPI做从机进行中断数据传输(SPI从机引脚与SPI主机引脚相连进行数据传输)。

spi_master_basic

spi_master_basic 描述了 SPI 模块最基本的数据传输方式,轮询方式。在样例工程中,通过  SPI_InitMaster() 函数,配置 SPI 为主模式,选定一个 SPI 数据采样时序,为空闲时时钟线为低电平、下降沿时数据有效的 SPI_PolPha_Alt0 ,并指定传输数据宽度为 SPI_DataWidth_8b 、SPI 的波特率为 BOARD_LOOP_SPI_BAUDRATE ,选定 SPI 的传输方向为 SPI_XferMode_TxRx 。然后,通过 SPI_Enable() 函数启动 SPI,使 SPI 能够进行数据传输。

每当 SPI 发送缓冲区未满时,可进行数据发送,数据由 MOSI 引脚传出;当 SPI 接收缓冲区接收有效数据时,可读取有效的接收数据。

在最终运行程序时,需要将 SPI 的 MOSI 引脚与 MISO 引脚使用杜邦线相连,通过向 PC 机上的串口通信终端输入任意按键,使 SPI 进行 APP_SPI_BUFF_LEN 次数据发送并接收到数据,主循环对接收数据和发送数据进行验证,若存在验证失败数据,则可看到串口打印出错信息 spi loopback xfer error. app_spi_xfer_err_count =  ,若验证成功,则可看到串口打印 spi loopback xfer done. ,从而验证 SPI 做主机的数据轮询传输情况。

spi_master_tx_dma

spi_master_tx_dma 相较于 spi_master_basic 样例工程,实现通过 DMA 进行 SPI 的数据发送功能。

其实现原理,是在 spi_master_basic 的基础上,增加对于 DMA 的初始化,通过 DMA_InitChannel() 函数,选定所使用的 DMA 通道为 BOARD_SPI_TX_DMA_CHANNEL ,配置 DMA 数据搬运方向为存储器到外设 DMA_XferMode_MemoryToPeriph ,数据宽度为 DMA_XferWidth_8b ,通过 SPI_GetTxDataRegAddr() 函数获取外设地址并进行配置,设定存储器地址为 (uint32_t)app_spi_tx_buf ,外设地址自增模式为不自增 DMA_AddrIncMode_StayAfterXfer ,存储器地址自增模式为 DMA_AddrIncMode_IncAfterXfer。然后,启用选定 SPI 外设的 DMA 发送通道对应的 NVIC 中断。最后,通过 DMA_EnableChannelInterrupts() 函数,在 BOARD_SPI_TX_DMA_CHANNEL 通道使能 DMA_CHN_INT_XFER_DONE 中断,每次在 DMA_EnableChannel() 启动 DMA 数据传输后,DMA 传输完成时,会触发中断。

实际运行程序时,将 SPI 的 MOSI 引脚与 MISO 引脚使用杜邦线相连后,用户在串口调试终端中每次输入任意字符,程序均会调用一次 DMA_EnableChannel() 函数,启动 DMA 传输,在 SPI 进行一次 DMA 传输并完成后,DMA 输标志位`app_dma_xfer_done`将在中断处理函数中被设置为true,主程序循环等待,当标志位为 true 时,打印 spi tx dma xfer done. 到串口终端界面。

spi_master_xfer_dma

spi_master_xfer_dma 相较于 spi_master_tx_dma 样例工程,实现通过 DMA 进行 SPI 的数据收发功能。

其实现原理,是在 spi_master_tx_dma 的基础上,增加对于 SPI 使用 DMA 接收通道的初始化,选定使用的 SPI DMA 接收通道为 BOARD_SPI_RX_DMA_CHANNEL ,在初始化 DMA 发送通道后,继续使用 DMA_InitChannel() 函数初始化 DMA 接收通道,修改传输方向为 DMA_XferMode_PeriphToMemory ,设定存储器地址为 (uint32_t)app_spi_rx_buf ,通过 SPI_GetRxDataRegAddr() 函数获取外设地址并配置。然后启用选定 SPI 外设的 DMA 接收通道对应的 NVIC 中断。最后,在 BOARD_SPI_RX_DMA_CHANNEL 通道使能 DMA_CHN_INT_XFER_DONE 中断,每次在 DMA_EnableChannel() 启动 DMA 数据传输后,当 DMA 传输完成,会触发对应通道的中断。

实际运行时,将 SPI 的 MOSI 引脚与 MISO 引脚使用杜邦线相连后,用户在串口调试终端中每次输入任意字符,程序均会调用一次 DMA_EnableChannel() 函数,启动 DMA 的发送与接收通道,在 SPI 进行一次 DMA 发送并完成后,DMA 发送完成标志位 app_dma_tx_done 将在发送通道所对应的中断处理函数中被设置为 true,再进行 SPI 的 DMA 接收操作,DMA 接收数据完成后,DMA 接收完成标志位 app_dma_rx_done 将在接收通道所对应的中断处理函数中被设置为 true,主程序循环等待,当发送完成标志位为 true 时,打印 spi tx dma done. 到串口终端界面,当接收完成标志位未 true 时,打印 spi rx dma done with data: 以及接收数据到串口终端界面。

spi_master_b2b_interrupt

spi_master_b2b_interrupt 实现的是一个使用 SPI 主模式中断传输的样例工程,在主从机的从属关系中做主机,需与从机结合使用。

其实现原理,是在 spi_master_basic 的基础上,增加 SPI 传输所使用的结构体,通过 SPI_EnableInterrupts() 函数使能发送完成中断 SPI_INT_TX_DONE 与接收完成中断 SPI_INT_RX_DONE 中断,当发送或接收数据完成时,产生对应中断,在中断处理函数中,传输次数达到所设定的 rx_idx 或 tx_idx ,将通过 SPI_EnableInterrupts() 函数关闭对应的接收完成中断或发送完成中断,接收完成后调用 spi_rx_done_callback() 回调函数,并将全局标志位 app_spi_xfer_flag 置为true。

实际运行此程序时,需令一块开发板下载此样例工程,另一块开发板下载 spi_slave_b2b_interrupt 样例工程,使用杜邦线连接两开发板的 MOSI、MISO、NSS、SCK 引脚,通过串口终端界面输入任意按键,进行 SPI 的数据收发,串口终端打印发送数据与接收数据。

spi_master_b2b_polling

spi_master_b2b_polling 实现的是一个使用 SPI 主模式轮询传输的样例工程,在主从机的从属关系中做主机,需与从机结合使用。

该实现原理与 spi_master_basic 基本相同,通过 while 循环等待当前达到发送或接收数据所需的传输条件,达到后进行数据收发,设定发送 APP_SPI_BUF_LEN 个数据并接收。

实际运行此程序时,需令一块开发板下载此样例工程,另一块开发板下载 spi_slave_b2b_interrupt 样例工程,使用杜邦线连接两开发板的 MOSI、MISO、NSS、SCK 引脚,通过串口终端界面输入任意按键,进行 SPI 的数据收发,串口终端打印发送数据与接收数据。

spi_slave_b2b_interrupt

spi_slave_b2b_interrupt 实现的是一个使用 SPI 从模式中断的样例工程,在主从机的从属关系中做从机,需与主机结合使用。

在样例工程中,通过 SPI_InitSlave() 函数配置 SPI 为从模式,设置传输方向为 SPI_XferMode_TxRx ,SPI 从机的数据采样时序需要与主机的数据采样时序配置相同,为 SPI_PolPha_Alt0 ,设定数据宽度为 SPI_DataWidth_8b 。通过 SPI_EnableInterrupts() 函数使能 SPI_INT_RX_DONE 中断,并启用选定 SPI 外设对应的 NVIC 中断。最后,启动 SPI,SPI 作为从机,等待主机的命令。当 SPI 接收完成数据后,产生接收完成中断,并执行中断处理函数,在中断处理函数中将已接收的数据发送出去。

在最终运行程序时,需令一块开发板下载此样例工程,另一块开发板下载 spi_master_b2b_polling 样例工程或 spi_master_b2b_interrupt 样例工程,使用杜邦线连接两开发板的 MOSI、MISO、NSS、SCK 引脚,在 spi_master_b2b_xxx 工程中对应的串口终端界面下输入任何字符,在 spi_slave_b2b_interrupt 工程中对应的串口终端界面中可以看到对应的内容。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 21

业界领先的半导体器件供应商兆易创新GigaDevice(股票代码 603986)今日宣布,率先推出采用3mm×3mm×0.4mm FO-USON8封装的SPI NOR Flash——GD25LE128EXH,其最大厚度仅为0.4mm,容量高达128Mb,是目前业界在此容量上能实现的最小塑封封装产品,可在应对大容量代码存储需求的同时,提供极大限度的紧凑型设计自由。

近年来,随着物联网、可穿戴、健康监护、网通等应用的快速发展,市场需求变化多样,不仅要在精致小巧的产品形态中提供丰富的功能,还要具有极低功耗,以保障产品的长时间工作。而SPI NOR Flash作为这些设备中重要的代码存储单元,需提供更小、更薄、更轻的产品选择来满足这些不断变化的应用需求。

1.png

GD25LE系列SPI NOR Flash是兆易创新旗舰型低功耗产品,此次新推出的3mm×3mm×0.4mm FO-USON8 GD25LE128EXH产品延续了LE系列的优异性能,其最高时钟频率133MHz,数据吞吐量高达532Mbit/s,极大提升了客户的系统访问速度和开机效率,同时在4通道133MHz时,读功耗仅为6mA,与行业同类产品相比,降低了45%的功耗,有效延长设备的续航时间。并且,更为重要的是GD25LE128EXH实现了128Mb容量产品上的超小尺寸,此前,业界128Mb容量产品的主流封装为6mm×5mm×0.8mm WSON8,而GD25LE128EXH采用的3mm×3mm×0.4mm超小尺寸的新型封装,面积缩小达70%,厚度减薄50%,能够显著节省85%的空间体积,并节省材料成本。

除了尺寸优势显著提升之外,3mm×3mm×0.4mm FO-USON8 GD25LE128EXH与64Mb及以下容量的3mm×4mm×0.6mm USON8封装产品引脚兼容,无需调整PCB布局即可快速升级容量至128Mb,对于不同容量需求的方案,进一步简化了其兼容性要求的设计。

兆易创新存储事业部执行总监陈晖先生表示:“作为业界领先的Fabless芯片供应商,兆易创新不仅是创新技术的提倡者,同时也是先进技术的践行者。此次率先推出超小尺寸3mm×3mm×0.4mm FO-USON8封装的128Mb SPI NOR Flash,完美匹配了业界对小型化和高集成度的要求,并且基于此封装技术落地的首款产品GD25LE128EXH所具备的低功耗特性也使其适用于任何以电池供电的产品设计中。未来随着智能设备的不断升级,兆易创新预见大容量、小尺寸、低功耗的SPI NOR Flash产品将持续被业界需要。针对这一趋势,公司正在规划通过先进封装和存储技术开发其它容量、更小尺寸的存储产品,为客户提供更多样化的选择。” 

GD25LE128EXH现已量产,客户可联络销售代表或授权代理商了解相关信息。另外,同系列3mm×2mm×0.4mm FO-USON8封装产品GD25LE64E也即将在5月底提供样片,具体详情请联络销售代表。

来源:兆易创新GigaDevice

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 20

随着技术的进步,低功耗物联网(IoT)和边缘/云计算需要更精确的数据传输。图1展示的无线监测系统是一个带有24位模数转换器(ADC)的高精度数据采集系统。在此我们通常会遇到这样一个问题,即微控制单元(MCU)能否为数据转换器提供高速的串行接口。

本文描述了设计MCU和ADC之间的高速串行外设接口(SPI)关于数据事务处理驱动程序的流程,并简要介绍了优化SPI驱动程序的不同方法及其ADC与MCU配置。本文还详细介绍了SPI和直接存储器访问(DMA)关于数据事务处理的示例代码。最后,本文演示了在不同MCU(ADuCM4050、MAX32660)中使用相同驱动程序时ADC的吞吐率。

1.png

图1. 状态监控。

通用SPI驱动程序简介

通常,MCU厂商会在例程代码中提供通用的SPI驱动程序/API。通用SPI驱动程序/API通常可以涵盖大多数用户的应用,这些代码可能包含许多配置或判断语句。但在某些特定情况下,比如ADC数据采集,通用的SPI驱动程序可能无法满足ADC数据的全速的吞吐速率需求,因为通用的驱动程序中有过多的配置,而未使用的配置会产生额外的开销并导致时间延迟。

2.png

图2. 通用API的配置。

设计思路与实践框架

我们通常会选择低功耗高性能的MCU作为主机通过SPI提取ADC的输出数据。但是,由于ADI的SPI驱动程序的数据事务处理命令存在冗余,因此数据输出速率可能被显著降低。为了充分释放ADC的潜在速率,本文使用ADuCM4050和AD7768-1进行实验并尝试可能的解决方案。尽管在使用默认滤波器的情况下,ADuCM4050的最大数据输出速率可达256 kHz,但在当前情况下,其速率被限制在8 kHz。提高输出速率的潜在解决方案包括删除不必要的命令以及激活DMA控制器。本文将在以下小节中介绍这些思路。

3.png

图3. 不同ODR以及DRDY与SCLK之间的关系。

以MCU作为主机

ADuCM4050 MCU是一款主时钟速率为26 MHz的超低功耗微控制器,内核为ARM® Cortex®-M4F处理器。ADuCM4050配有三个SPI,每个SPI都有两个DMA通道(接收和发射通道)可与DMA控制器连接。DMA控制器和DMA通道可实现存储器与外设之间的数据传输。这是一种高效的数据分配方法,可将内核释放以处理其他任务。

以ADC作为从机

AD7768-1是一款24位低功耗、高性能的Σ-Δ ADC。其数据输出速率 (ODR)和功耗模式均可根据用户的要求进行配置。ODR由抽取系数和功耗模式共同决定,如表1中所示。

4.png

表1. 数据输出速率的功耗模式配置

AD7768-1的连续读取模式也是该产品的一个重要特性。ADC的输出数据存储在寄存器0x6C中。一般而言,每次读/写操作之前,ADC寄存器中的数据都需要地址才可以访问,但是连续读取模式则支持在收到每个数据就绪信号后直接从0x6C寄存器提取数据。ADC的输出数据为24位的数字信号,对应的电压如表2 所示。

5.png

表2. 数字输出码和模拟输入电压

引脚连接示意图

ADuCM4050和AD7768-1组成的数据事务处理示例模型的引脚连接如图4所示。

6.png

图4. AD7768-1和ADuCM4050的接口引脚连接。

ADC的复位信号引脚RST_1连接至MCU的GPIO28,而数据就绪信号引脚DRDY_1则连接至MCU的GPIO27。其余引脚则根据通用的SPI配置标准进行连接,其中MCU为主机,而ADC为从机。SDI_1接收MCU发送的ADC寄存器读/写命令,而DOUT_1则将ADC的输出数据发送至MCU。

数据事务处理的实现

中断数据事务处理

为实现连续数据事务处理,本文将MCU的GPIO27引脚(连接至ADC 的DRDY_1引脚)用作中断触发引脚。ADC将数据就绪信号发送至 GPIO27时会触发MCU包含数据事务处理命令的中断回调函数。如图5所示,数据采集必须在中断A和中断B之间的时间间隔内进行。

7.png

图5. 两次中断的时间间隔。

利用ADI的SPI驱动程序可以在ADC和MCU之间轻松实现数据事务处理。但是,由于驱动程序内存在冗余命令,ADC的ODR会被限制在8 kHz。本文尽可能地精简了代码以加快ODR,将介绍实现DMA 数据事务处理的两种方法:基本模式的DMA事务处理和乒乓模式 的DMA事务处理。

基本模式的DMA事务处理

在实现每个DMA事务处理之前需要对SPI和DMA进行配置(参见图6 中的示例代码)。SPI_CTL为SPI配置,其值为0x280f,源于ADI的SPI 驱动程序的设定值。SPI_CNT为传输字节数。由于每个DMA事务处理只能发送固定的16位数据,因此SPI_CNT必须是2的倍数。本例设置SPI_CNT为4,以满足ADC的24位的输出数据要求。SPI_DMA寄存器为SPI的DMA使能寄存器,设定其值为0x5以使能DMA接收请求。命令pADI_DMA0->EN_SET=(1<<5)使能第五个通道的DMA,即SPI0 RX。

8.png

图6. 基本DMA事务处理模式的代码。

每个DMA通道都有一个DMA结构寄存器,如表3中所示。需要指出的是,这里的数据来源地址的结尾(即SPI0 Rx,亦即来源端指针SRC_END_PTR)在整个操作期间无需增加,因为Rx FIFO会自动 将寄存器中的数据推送出去。另一方面,数据目标地址的结尾(即目标端指针DST_END_PTR)根据ADI的SPI驱动程序的使用函数计算得出,即目标地址+ SPI_CNT -2。

9.png

表3. DMA结构寄存器

当前地址为内部数组缓冲区的地址。DMA控制数据配置CHNL_CFG 包括来源数据大小、来源地址增量、目标地址增量、剩余传输次数和DMA控制模式等设置,其值0x4D000011按照表4中所述的设置配置。

10.png

表4. 控制数据配置0x4D00011的DMA配置

SCLK时钟通过伪读取命令SPI_SPI0 -> RX启动,输出数据通过MISO 从ADC传至MCU。MOSI上其它的数据传输可以忽略不计。一旦完成 Rx的FIFO填充,DMA请求就会生成从而激活DMA控制器,以将数据从 DMA来源地址(即SPI0 Rx FIFO)传输至DMA目标地址(即内部数组的缓冲区)。值得注意的是,SPI_DMA=0x3时会生成Tc请求。

最后,通过将当前目标地址加4的方式将目标地址用于下一个4 字节的传输。

请注意,SPI0 DMA通道的pADI_DMA0->DSTADDR_CLR和pADI_ DMA0->RMSK_CLR必须在首次中断触发之前在主函数中设置。前一个为DMA通道目标地址减量使能清零寄存器,用于在增量模式下设置每次DMA传输后的目标地址移位(目标地址计算函数仅在增量模式下有效)。后一个为DMA通道请求屏蔽清零寄存器,用于将通道的DMA请求状态清零。

基本模式的DMA事务处理时间图如图7a所示。图中三个时隙分别代表DRDY信号、SPI/DMA设置和DMA数据事务处理。在该模式中,CPU的空闲时间较多,因此希望DMA控制器在处理数据传输时能将任务分配给CPU。

11.png

图7. (a)基本模式DMA和(b)乒乓模式的时间图。

乒乓模式的DMA事务处理

在执行伪读取命令后,DMA控制器会开始数据事务处理,从而使得MCU的CPU处于空闲状态而不处理任何任务。如果能够让CPU和 DMA控制器同时工作,那么任务处理就从串行模式转变为并行模式。这样,就可以同时进行DMA配置(通过CPU)以及DMA数据事务处理(通过DMA控制器)。为实现这一思路,需要设置DMA控制器处于乒乓模式。乒乓模式将两组DMA结构进行了整合:主结构和备用结构。每次DMA请求时,DMA控制器会在两组结构之间自动切换。变量p的初始设置为0,其值表示是主DMA结构(p = 0) 还是备用DMA结构(p = 1)负责数据事务处理。如果p = 0,则在收到伪读取命令时启动主DMA结构进行数据事务处理,同时会为备用DMA结构分配值,使其在下一个中断周期内负责数据事务处理。如果p = 1,则主结构和备用结构的作用互换。当仅有主结构处于基本DMA模式时,在DMA事务处理期间对DMA结构的修改会失败。乒乓模式使得CPU能够访问和写入备用DMA结构,而DMA控制器可以读取主结构,反之亦然。如图7b所示,由于DMA的结构配置是在最后一个周期内完成的,因此在DRDY信号从ADC传送至 MCU后DMA数据事务处理可以被立即执行,使得CPU和DMA同时工作而无需等待。现在,ADC的ODR得到了提升空间,因为总的工作时间已大大缩短。

中断处理程序的优化

两次DRDY信号之间的时间间隔不仅包括了中断回调函数的命令执行时间,还包括了ADI的GPIO中断处理函数的命令执行时间。

当MCU启动时,CPU会运行启动文件(即startup.s)。所有事件的处理函数均在该文件中定义,包括GPIO中断处理函数。一旦触发GPIO中断,CPU就会执行中断处理函数(即ADI的GPIO驱动程序中的GPIO_A_INT_HANDLER和GPIO_B_INT_HANDLER)。通用的中断处理函数会在所有的GPIO引脚中搜索触发中断的引脚并清零其中断状态、运行回调函数。由于DRDY是本文应用的唯一中断信号,因此可以对函数进行简化以加快进程。可选的解决方案包括 (1)在启动文件中重新定位目标,以及(2)修改原始的中断处理函数。重新定位目标意味着自定义中断处理函数,并替换启动文件中的原始的中断处理函数。

而修改原始的中断处理函数只需要添加一个自定义的GPIO驱动程序。本文采用第二种方案修改原始的中断处理函数,如图8所示。该方案只将连接至DRDY的GPIO的引脚中断状态清零,并直接转到回调函数。请注意,这里需要通过取消选择build target中关于原始GPIO驱动函数的勾选框内容来隔离原始的GPIO驱动程序。

12.png

图8. 嵌套矢量中断控制器(NVIC)。

结果

速率性能

假定现在需要读取200个24位的ADC输出数据,并且SPI位速率设置为13 MHz。将DRDY信号和SCLK信号的引脚连接至示波器,可以通过观察DRDY信号与SPI数据事务处理(亦即DMA事务处理)启动之间的时间间隔的方法可以量化本文所述的每种方法对速率的改善程度。这里将DRDY信号至SCLK信号开始的时间间隔记为∆t,那么对于13 MHz的SPI速率,测量得出的∆t为:

  • (a)基本模式DMA Δt = 3.754 μs 

  • (b)乒乓模式DMA Δt = 2.8433 μs 

  • (c)乒乓模式DMA(使用优化的中断处理函数)Δt = 1.694 μs 

方法(a)和(b)可支持64 kHz的ODR,而方法(c)可支持128 kHz的ODR。这是因为方法(c)的∆t最短,从而使得SCLK信号能够更早结束。如果 SCLK信号(即数据事务处理)能在T/2之前完成(T为当前ADC的数据输出周期),则ODR可实现翻倍。这较之于原始的ADISPI驱动程序可以达到的8 kHz的ODR性能是一次巨大的进步。

13.png

图9. (a)基本模式DMA、(b)乒乓模式以及(c)乒乓模式(使用优化的中断处理函数)的Δt。

使用MAX32660控制AD7768-1

使用主时钟速率为96 MHz的MCU MAX32660控制AD7768-1)时的结果如何?在该情况下,使用优化的中断处理函数的中断设置,可在不使用DMA函数的情况下实现256 kHz的数据输出速率。参见图10。

14.png

图10. 不使用DMA时MAX32660的ODR。

结论

本文利用选定的ADC(AD7768-1)和MCU(ADuCM4050或MAX32660)通过 SPI实现了高速的数据事务处理。为实现速率优化的目标,本文简化了ADI的SPI驱动程序执行数据事务处理。此外本文提出,激活DMA控制器释放内核也可以加快连续数据事务处理的流程。在 DMA的乒乓模式下,DMA的配置时间可通过适当的调度来节省。在此基础上,还可以通过直接指定中断引脚的方式优化中断处理函数。在13 MHz的SPI位速率下,本文提出的方案的最佳性能可达到128 kSPS的ADC ODR。

15.png

表5. 使用ADuCM405和MAX32660实现的高速SPI连接

来源:亚德诺半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

SPI协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB在布局上节省了空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,它被广泛地使用在ADC、LCD、FLASH等设备与MCU之间的通信。

CKS32F4xx系列产品SPI介绍

CKS32F4xx系列的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为fpclk/2(CKS32F407型号的芯片默认fpclk为142MHz,fpclk2为84MHz),完全支持SPI协议的4种模式。SPI协议根据CPOL及CPHA的不同状态分成的四种工作模式如下表所示:

1.jpg

CKS32F4xx系列的SPI架构如下图所示:

2.png

图中的1处是SPI的引脚MOSI、MISO、SCK、NSS。CKS32F4xx芯片有多个 SPI外设,它们的SPI通讯信号引出到不同GPIO引脚上,使用时必须配置到这些指定的引脚。关于GPIO引脚的复用功能可以查阅芯片数据手册。各个引脚的作用介绍如下:

(1)NSS:从设备选择信号线,常称为片选信号线。当有多个SPI从设备与 SPI主机相连时,设备的其它信号线SCK、MOSI及MISO同时并联到相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3条总线;而每个从设备都有独立的一条NSS信号线,当主机要选择从设备时,把该从设备的NSS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以NSS线置低电平为开始信号,以NSS线被拉高作为结束信号。

(2)SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,两个设备之间通讯时,通讯速率受限于低速设备。

(3)MOSI:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。

(4)MISO:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

图中的2处是SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCK引脚的输出时钟频率。

图中的3处是SPI的数据控制逻辑。SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及MISO、MOSI线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写SPI 的“数据寄存器DR”把数据填充到发送缓冲区中,通过“数据寄存器DR”,可以获取接收缓冲区中的内容。其中数据帧的长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行还是 LSB先行。

图中的4处是SPI的整体控制逻辑。整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括SPI模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。实际应用中,我们一般不使用CKS32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

CKS32F4xx系列的SPI作为通讯主机端时收发数据的过程如下:

(1) 控制NSS信号线,产生起始信号;

(2) 把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;

(3) 通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中;

(4) 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;

(5) 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE标志位”为1时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。

假如我们使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收发“数据寄存器 DR”中的数据。

CKS32F4xx系列产品SPI的配置

接下来我们讲解如何利用CKS32F4xx系列固件库来完成对SPI的配置使用。跟其它外设一样,CKS32标准库提供了SPI初始化结构体及初始化函数来配置 SPI外设。了解初始化结构体后我们就能对SPI外设运用自如了,代码如下:

typedef struct
{  
    uint16_t SPI_Direction;            
    uint16_t SPI_Mode;                
    uint16_t SPI_DataSize;            
    uint16_t SPI_CPOL;               
    uint16_t SPI_CPHA;                 
    uint16_t SPI_NSS;                 
    uint16_t SPI_BaudRatePrescaler;    
    uint16_t SPI_FirstBit;             
    uint16_t SPI_CRCPolynomial;       
}SPI_InitTypeDef;

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) SPI_Direction:本成员设置SPI的通讯方向,可设置为双线全双工 (SPI_Direction_2Lines_FullDuplex),双线只接收 (SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。

2) SPI_Mode:本成员设置SPI工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通讯中的主机产生的。若被配置为从机模式,CKS32的SPI外设将接受外来的SCK信号:

3) SPI_DataSize: 本成员可以选择SPI通讯的数据帧大小是为8位 (SPI_DataSize_8b)还是16位(SPI_DataSize_16b)。

4) SPI_CPOL和SPI_CPHA: 这两个成员配置SPI的时钟极性CPOL和时钟相位CPHA,前面讲过这两个配置影响到SPI的通讯模式。时钟极性CPOL成员可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位CPHA则 可以设置为SPI_CPHA_1Edge(在SCK的奇数边沿采集数据)或 SPI_CPHA_2Edge(在SCK的偶数边沿采集数据)。

5) SPI_NSS: 本成员配置NSS引脚的使用模式,可以选择为硬件模式 (SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的SPI片选信号由 SPI硬件自动产生,而软件模式则需要我们自己把相应的GPIO端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。

6) SPI_BaudRatePrescaler: 本成员设置波特率分频因子,分频后的时钟即为SPI的SCK信号线的时钟频率。这个成员参数可设置为fpclk的2、4、6、8、16、32、64、128、256分频。可选的值如下所示:

SPI_BaudRatePrescaler_2    //2分频
SPI_BaudRatePrescaler_4    //4分频
SPI_BaudRatePrescaler_6    //6分频
SPI_BaudRatePrescaler_8    //8分频
SPI_BaudRatePrescaler_16   //16分频
SPI_BaudRatePrescaler_32   //32分频
SPI_BaudRatePrescaler_64   //64分频
SPI_BaudRatePrescaler_128  //128分频
SPI_BaudRatePrescaler_256  //256分频

7) SPI_FirstBit: 所有串行的通讯协议都会有MSB先行(高位数据在前)还是 LSB先行(低位数据在前)的问题,而CKS32F4xx系列的SPI模块可以通过这个结构体成员,对这个特性编程控制。

SPI_FirstBit_MSB    //高位数据在前
SPI_FirstBit_LSB     //低位数据在前

8) SPI_CRCPolynomial: 这是SPI的CRC校验中的多项式,若我们使用CRC 校验时,就使用这个成员的参数(多项式),来计算CRC的值。

配置完这些结构体成员的值,调用库函数SPI_Init即可把结构体的配置写入到寄存器中。

CKS32F4xx读写SPI FLASH实验

串口的DMA接发通信实验是存储器到外设和外设到存储器的数据传输。在第24

本小节以一种使用SPI通讯的串行FLASH存储芯片的读写实验为大家讲解 CKS32F4xx系列的SPI使用方法。实验中的FLASH芯片(型号:W25Q32)是一种使用SPI通讯协议的NORFLASH存储器,它的CS/CLK/DIO/DO引脚分别连接到了CKS32F4xx对应的SPI引脚NSS/SCK/MOSI/MISO上,其中CKS32F4xx的NSS引脚是一个普通的GPIO,不是SPI的专用NSS引脚,所以程序中我们要使用软件控制的方式。

1.编程要点

(1) 初始化通讯使用的目标引脚及端口时钟;  

(2) 使能SPI外设的时钟;

(3) 配置SPI外设的模式、地址、速率等参数并使能SPI外设;

(4) 编写基本SPI按字节收发的函数; 

(5) 编写对FLASH擦除及读写操作的的函数; 

(6) 编写测试程序,对读写数据进行校验。 

2.代码分析

代码清单1:W25Q32初始化配置

void W25QXX_Init(void)
{   
    GPIO_InitTypeDef  GPIO_InitStructure;  
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);   
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;   
    GPIO_Init(GPIOD, &GPIO_InitStructure); 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;  
    GPIO_Init(GPIOG, &GPIO_InitStructure);
    GPIO_SetBits(GPIOG,GPIO_Pin_3);   
    W25QXX_CS=1;                        //SPI FLASH不选中  
    SPI1_Init();                 //初始化SPI  
    SPI1_SetSpeed(SPI_BaudRatePrescaler_4);    //设置为21M时钟  
    W25QXX_TYPE=W25QXX_ReadID();         //读取FLASH ID.
}

上面的代码主要是完成对W25Q32片选引脚的初始化,SPI初始化。SPI通信速率设置和读取W25Q32的ID。

代码清单2:SPI初始化函数

void SPI1_Init(void)
{     
    GPIO_InitTypeDef  GPIO_InitStructure;  
    SPI_InitTypeDef  SPI_InitStructure;  
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;   
    GPIO_Init(GPIOB, &GPIO_InitStructure); 
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); 
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);   
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);  
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);  
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;      
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;    
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;      
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;   
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;      
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;    
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    
    SPI_InitStructure.SPI_CRCPolynomial = 7;    
    SPI_Init(SPI1, &SPI_InitStructure);    
    SPI_Cmd(SPI1, ENABLE);   
    SPI1_ReadWriteByte(0xff);   
}

上面这段代码主要是完成对SPI1的初始化,首先是配置了SPI1使用的引脚SPI1_SCK、SPI1_MOSI和SPI1_MISO。然后是根据第2小节的内容完成对SPI1外设模式的配置。根据FLASH芯片W25Q32的说明,它支持SPI模式0及模式3,支持双线全双工,使用MSB先行模式,支持最高通讯时钟为104MHz,数据帧长度为8位。我们要把CKS32F4的SPI外设中的这些参数配置一致。

代码清单3:SPI1单字节收发函数

u8 SPI1_ReadWriteByte(u8 TxData)
{              
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}   
    SPI_I2S_SendData(SPI1, TxData);   
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){}   
    return SPI_I2S_ReceiveData(SPI1); 
}

本函数中不包含SPI起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作。通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数SPI_I2S_SendData把要发送的数据“TxData”写入到SPI的数据寄存器DR,写入SPI数据寄存器的数据会存储到发送缓冲区,由SPI外设发送出去;写入完毕后等待RXNE事件,即接收缓冲区非空事件。由于SPI双线全双工模式下MOSI与MISO数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;等待至接收缓冲区非空时,通过调用库函数SPI_I2S_ReceiveData读取SPI的数据寄存器DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据作为SPI1_ReadWriteByte函数的返回值。

搞定了SPI的基本收发单元后,还需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制CKS32F4利用SPI总线向FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。具体的指令代码可以查看W25Q32芯片的数据手册。

代码清单4:读取FLASH芯片ID函数

u16 W25QXX_ReadID(void)
{  
    u16 Temp = 0;      
    W25QXX_CS=0;              
    SPI1_ReadWriteByte(0x90);        
    SPI1_ReadWriteByte(0x00);         
    SPI1_ReadWriteByte(0x00);         
    SPI1_ReadWriteByte(0x00);               
    Temp|=SPI1_ReadWriteByte(0xFF)<<8;    
    Temp|=SPI1_ReadWriteByte(0xFF);     
    W25QXX_CS=1;              
    return Temp;
}

这段代码利用控制CS引脚电平的宏“W25QXX_CS”以及前面编写的单字节收发函数SPI1_ReadWriteByte,很清晰地实现了读ID指令的时序,最后把读 取到的这3个数据合并到一个变量Temp中,然后作为函数返回值,把该返回值与我们定义的芯片ID对比,即可知道FLASH芯片是否正常。

代码清单5:W25Q32写使能和写禁止函数

void W25QXX_Write_Enable(void)   
{  
    W25QXX_CS=0;                                
    SPI1_ReadWriteByte(W25X_WriteEnable);      
    W25QXX_CS=1;                                    
}
void W25QXX_Write_Disable(void)   
{    
    W25QXX_CS=0;                              
    SPI1_ReadWriteByte(W25X_WriteDisable);          
    W25QXX_CS=1;                                      
}

由于FLASH存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”, 那就不修改存储矩阵,在要存储数据“0”时,需要更改该位。W25Q32支持“扇区擦除”、“块擦除”以及“整片擦除”。 扇区擦除指令的第一个字节为指令编码,紧接着发送的3个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。

代码清单6:W25Q32扇区擦除函数

void W25QXX_Erase_Sector(u32 Dst_Addr)   
{         
    Dst_Addr*=4096;    
    W25QXX_Write_Enable();                        
    W25QXX_Wait_Busy();       
    W25QXX_CS=0;                                  
    SPI1_ReadWriteByte(W25X_SectorErase);          
    SPI1_ReadWriteByte((u8)((Dst_Addr)>>16));         
    SPI1_ReadWriteByte((u8)((Dst_Addr)>>8));       
    SPI1_ReadWriteByte((u8)Dst_Addr);    
    W25QXX_CS=1;                                       
    W25QXX_Wait_Busy();             
}

目标扇区被擦除完毕后,就可以向它写入数据了。与EEPROM类似,FLASH芯片也有页写入命令,使用页写入命令最多可以一次向FLASH传输256个字节的数据,我们把这个单位称为页大小。在进行页写入时第1个字节为“页写入指令”编码,2-4字节为要写入的“地址A”,接着的是要写入的内容,最多可以发送 256字节数据,这些数据将会从“地址A”开始,按顺序写入到FLASH的存储矩阵。若发送的数据超出256个,则会覆盖前面发送的数据。

代码清单7:W25Q32页写入函数

void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{   
    u16 i;      
    W25QXX_Write_Enable();                   
    W25QXX_CS=0;                                   
    SPI1_ReadWriteByte(W25X_PageProgram);             
    SPI1_ReadWriteByte((u8)((WriteAddr)>>16));       
    SPI1_ReadWriteByte((u8)((WriteAddr)>>8));       
    SPI1_ReadWriteByte((u8)WriteAddr);       
    for(i=0;i<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);   
    W25QXX_CS=1;                              
    W25QXX_Wait_Busy();            
}

应用的时候我们常常要写入不定量的数据,直接调用“页写入”函数并不是特别方便,所以我们页写入函数的基础上编写了“不定量数据写入”的函数。在实际调用这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态

代码清单8:W25Q32不定量数据写入函数

void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{   
    u32 secpos;  
    u16 secoff;  
    u16 secremain;        
    u16 i;      
    u8 * W25QXX_BUF;         
    W25QXX_BUF=W25QXX_BUFFER;          
    secpos=WriteAddr/4096;   
    secoff=WriteAddr%4096;   
    secremain=4096-secoff;     
    if(NumByteToWrite<=secremain)secremain=NumByteToWrite;  
    while(1)   
    {      
        W25QXX_Read(W25QXX_BUF,secpos*4096,4096);    
        for(i=0;i<secremain;i++)     
        {      
            if(W25QXX_BUF[secoff+i]!=0XFF)break;         
        }    
        if(i<secremain)     
        {      
            W25QXX_Erase_Sector(secpos);       
            for(i=0;i<secremain;i++)         
            {    
                W25QXX_BUF[i+secoff]=pBuffer[i];          
            }      
            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);     
        }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);     
        if(NumByteToWrite==secremain)break;     
        else     
        {      
            secpos++;      
            secoff=0;            
            pBuffer+=secremain;        
            WriteAddr+=secremain;              
            NumByteToWrite-=secremain;              
            if(NumByteToWrite>4096)secremain=4096;        
            else secremain=NumByteToWrite;          
        }     
    };   
}

函数的入口参数pBuffer是数据存储区、WriteAd是开始写入的地址(24bit)、NumByteToWrite是要写入的字节数(最大65535)gaojp。

相对于写入,FLASH芯片W25Q32的数据读取要简单的多,发送了指令编码及要读的起始地址和要读取的字节数之后,FLASH 芯片W25Q32就会按地址递增的方式返回存储矩阵中一定字节数量的数据。

代码清单9:W25Q32读取数据函数

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{    
    u16 i;                             
    W25QXX_CS=0;                                  
    SPI1_ReadWriteByte(W25X_ReadData);              
    SPI1_ReadWriteByte((u8)((ReadAddr)>>16));         
    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));       
    SPI1_ReadWriteByte((u8)ReadAddr);       
    for(i=0;i<NumByteToRead;i++)  
    {         
        pBuffer[i]=SPI1_ReadWriteByte(0XFF);   //循环读数      
    }  
    W25QXX_CS=1;                      
}

函数的入口参数pBuffer是数据存储区、ReadAddr是开始读取的地址(24bit)、NumByteToRead是要读取的字节数(最大65535)。

完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程。

代码清单10:W25Q32读写测试函数

uint8_t w25q32_Test(void)
{  
    u16 i;   
    printf("写入的数据:\r\n");  
    for ( i=0; i<=10; i++ )   
    {       
        spi_Buf_Write[i] = i;    
        printf("0x%02X ", spi_Buf_Write[i]);   
    }   
    W25QXX_Write((u8*)spi_Buf_Write,FLASH_SIZE-100,11);          
    printf("写成功,");     
    printf("读出的数据:\r\n");    
    W25QXX_Read(datatemp,FLASH_SIZE-100,11);            
    for (i=0; i<11; i++)  
    {      
        if(datatemp[i] != spi_Buf_Write[i])    
        {      
            printf("0x%02X ", datatemp[i]);      
            printf("错误:I2C EEPROM写入与读出的数据不一致");     
            return 0;    
        }    
        printf("0x%02X ", datatemp[i]);  
    }  
    printf("\r\n");    
    printf("spi(w25q32)读写测试成功");  
    return 1;
}

代码中先填充一个数组,数组的内容为0,1至10,接着把这个数组的内容写入到SPI FLASH中,并将写入的数据打印输出到串口调试助手。写入完毕后再从SPI FLASH的地址中读取数据,把读取到的数据与写入的数据进行校验,若一致说明读写正常,否则读写过程有问题或者SPI FLASH芯片不正常,然后再将读取到的数据打印输出到串口调试助手。

代码清单11:主函数

int main(void)
{     
    u16 id = 0;    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
    delay_init(168);       
    USART_Configuration();  
    W25QXX_Init();        
    while(1)  
    {    
        id = W25QXX_ReadID();    
        if (id == W25Q32 || id == NM25Q32)      
            break;     
            printf("W25Q32 init failed\r\n");    
        delay_ms(500);    
        delay_ms(500);  
    }   
    printf("W25Q32 init success\r\n");   
    w25q32_Test(); 
     while(1)  
     {   
     }       
 }

主函数代码比较简单,主要是完成串口初始化和W25Q32的初始化,初始化完成之后会执行W25QXX_ReadID函数,读取W25Q32的ID,同时对ID进行判断,并将结果通过串口调试助手打印输出。然后会执行一次W25Q32测试函数,并将一些测试结果通过串口调试助手打印输出。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 33

随着技术的进步,低功耗物联网(IoT)和边缘/云计算需要更精确的数据传输。图1展示的无线监测系统是一个带有24位模数转换器(ADC)的高精度数据采集系统。在此我们通常会遇到这样一个问题,即微控制单元(MCU)能否为数据转换器提供高速的串行接口。

本文描述了设计MCU和ADC之间的高速串行外设接口(SPI)关于数据事务处理驱动程序的流程,并简要介绍了优化SPI驱动程序的不同方法及其ADC与MCU配置。本文还详细介绍了SPI和直接存储器访问(DMA)关于数据事务处理的示例代码。最后,本文演示了在不同MCU(ADuCM4050、MAX32660)中使用相同驱动程序时ADC的吞吐率。

1.png

图1. 状态监控。

通用SPI驱动程序简介

通常,MCU厂商会在例程代码中提供通用的SPI驱动程序/API。通用SPI驱动程序/API通常可以涵盖大多数用户的应用,这些代码可能包含许多配置或判断语句。但在某些特定情况下,比如ADC数据采集,通用的SPI驱动程序可能无法满足ADC数据的全速的吞吐速率需求,因为通用的驱动程序中有过多的配置,而未使用的配置会产生额外的开销并导致时间延迟。

2.png

图2. 通用API的配置。

设计思路与实践框架

我们通常会选择低功耗高性能的MCU作为主机通过SPI提取ADC的输出数据。但是,由于ADI的SPI驱动程序的数据事务处理命令存在冗余,因此数据输出速率可能被显著降低。为了充分释放ADC的潜在速率,本文使用ADuCM4050和AD7768-1进行实验并尝试可能的解决方案。尽管在使用默认滤波器的情况下,ADuCM4050的最大数据输出速率可达256 kHz,但在当前情况下,其速率被限制在8 kHz。提高输出速率的潜在解决方案包括删除不必要的命令以及激活DMA控制器。本文将在以下小节中介绍这些思路。

3.png

图3. 不同ODR以及DRDY与SCLK之间的关系。

以MCU作为主机

ADuCM4050 MCU是一款主时钟速率为26 MHz的超低功耗微控制器,内核为ARM® Cortex®-M4F处理器。ADuCM4050配有三个SPI,每个SPI都有两个DMA通道(接收和发射通道)可与DMA控制器连接。DMA控制器和DMA通道可实现存储器与外设之间的数据传输。这是一种高效的数据分配方法,可将内核释放以处理其他任务。

以ADC作为从机

AD7768-1是一款24位低功耗、高性能的Σ-Δ ADC。其数据输出速率 (ODR)和功耗模式均可根据用户的要求进行配置。ODR由抽取系数和功耗模式共同决定,如表1中所示。

表1. 数据输出速率的功耗模式配置

4.png

AD7768-1的连续读取模式也是该产品的一个重要特性。ADC的输出数据存储在寄存器0x6C中。一般而言,每次读/写操作之前,ADC寄存器中的数据都需要地址才可以访问,但是连续读取模式则支持在收到每个数据就绪信号后直接从0x6C寄存器提取数据。ADC的输出数据为24位的数字信号,对应的电压如表2 所示。

表2. 数字输出码和模拟输入电压

5.png

引脚连接示意图

ADuCM4050和AD7768-1组成的数据事务处理示例模型的引脚连接如图4所示。

6.png

图4. AD7768-1和ADuCM4050的接口引脚连接。

ADC的复位信号引脚RST_1连接至MCU的GPIO28,而数据就绪信号引脚DRDY_1则连接至MCU的GPIO27。其余引脚则根据通用的SPI配置标准进行连接,其中MCU为主机,而ADC为从机。SDI_1接收MCU发送的ADC寄存器读/写命令,而DOUT_1则将ADC的输出数据发送至MCU。

数据事务处理的实现

中断数据事务处理

为实现连续数据事务处理,本文将MCU的GPIO27引脚(连接至ADC 的DRDY_1引脚)用作中断触发引脚。ADC将数据就绪信号发送至 GPIO27时会触发MCU包含数据事务处理命令的中断回调函数。如图5所示,数据采集必须在中断A和中断B之间的时间间隔内进行。

7.png

图5. 两次中断的时间间隔。

利用ADI的SPI驱动程序可以在ADC和MCU之间轻松实现数据事务处理。但是,由于驱动程序内存在冗余命令,ADC的ODR会被限制在8 kHz。本文尽可能地精简了代码以加快ODR,将介绍实现DMA 数据事务处理的两种方法:基本模式的DMA事务处理和乒乓模式 的DMA事务处理。

基本模式的DMA事务处理

在实现每个DMA事务处理之前需要对SPI和DMA进行配置(参见图6 中的示例代码)。SPI_CTL为SPI配置,其值为0x280f,源于ADI的SPI 驱动程序的设定值。SPI_CNT为传输字节数。由于每个DMA事务处理只能发送固定的16位数据,因此SPI_CNT必须是2的倍数。本例设置SPI_CNT为4,以满足ADC的24位的输出数据要求。SPI_DMA寄存器为SPI的DMA使能寄存器,设定其值为0x5以使能DMA接收请求。命令pADI_DMA0->EN_SET=(1<<5)使能第五个通道的DMA,即SPI0 RX。

8.png

图6. 基本DMA事务处理模式的代码。

每个DMA通道都有一个DMA结构寄存器,如表3中所示。需要指出的是,这里的数据来源地址的结尾(即SPI0 Rx,亦即来源端指针SRC_END_PTR)在整个操作期间无需增加,因为Rx FIFO会自动 将寄存器中的数据推送出去。另一方面,数据目标地址的结尾(即目标端指针DST_END_PTR)根据ADI的SPI驱动程序的使用函数计算得出,即目标地址+ SPI_CNT -2。

表3. DMA结构寄存器

9.png

当前地址为内部数组缓冲区的地址。DMA控制数据配置CHNL_CFG 包括来源数据大小、来源地址增量、目标地址增量、剩余传输次数和DMA控制模式等设置,其值0x4D000011按照表4中所述的设置配置。

表4. 控制数据配置0x4D00011的DMA配置

10.png

SCLK时钟通过伪读取命令SPI_SPI0 -> RX启动,输出数据通过MISO 从ADC传至MCU。MOSI上其它的数据传输可以忽略不计。一旦完成 Rx的FIFO填充,DMA请求就会生成从而激活DMA控制器,以将数据从 DMA来源地址(即SPI0 Rx FIFO)传输至DMA目标地址(即内部数组的缓冲区)。值得注意的是,SPI_DMA=0x3时会生成Tc请求。

最后,通过将当前目标地址加4的方式将目标地址用于下一个4 字节的传输。

请注意,SPI0 DMA通道的pADI_DMA0->DSTADDR_CLR和pADI_ DMA0->RMSK_CLR必须在首次中断触发之前在主函数中设置。前一个为DMA通道目标地址减量使能清零寄存器,用于在增量模式下设置每次DMA传输后的目标地址移位(目标地址计算函数仅在增量模式下有效)。后一个为DMA通道请求屏蔽清零寄存器,用于将通道的DMA请求状态清零。

基本模式的DMA事务处理时间图如图7a所示。图中三个时隙分别代表DRDY信号、SPI/DMA设置和DMA数据事务处理。在该模式中,CPU的空闲时间较多,因此希望DMA控制器在处理数据传输时能将任务分配给CPU。

11.png

图7. (a)基本模式DMA和(b)乒乓模式的时间图。

乒乓模式的DMA事务处理

在执行伪读取命令后,DMA控制器会开始数据事务处理,从而使得MCU的CPU处于空闲状态而不处理任何任务。如果能够让CPU和 DMA控制器同时工作,那么任务处理就从串行模式转变为并行模式。这样,就可以同时进行DMA配置(通过CPU)以及DMA数据事务处理(通过DMA控制器)。为实现这一思路,需要设置DMA控制器处于乒乓模式。乒乓模式将两组DMA结构进行了整合:主结构和备用结构。每次DMA请求时,DMA控制器会在两组结构之间自动切换。变量p的初始设置为0,其值表示是主DMA结构(p = 0) 还是备用DMA结构(p = 1)负责数据事务处理。如果p = 0,则在收到伪读取命令时启动主DMA结构进行数据事务处理,同时会为备用DMA结构分配值,使其在下一个中断周期内负责数据事务处理。如果p = 1,则主结构和备用结构的作用互换。当仅有主结构处于基本DMA模式时,在DMA事务处理期间对DMA结构的修改会失败。乒乓模式使得CPU能够访问和写入备用DMA结构,而DMA控制器可以读取主结构,反之亦然。如图7b所示,由于DMA的结构配置是在最后一个周期内完成的,因此在DRDY信号从ADC传送至 MCU后DMA数据事务处理可以被立即执行,使得CPU和DMA同时工作而无需等待。现在,ADC的ODR得到了提升空间,因为总的工作时间已大大缩短。

中断处理程序的优化

两次DRDY信号之间的时间间隔不仅包括了中断回调函数的命令执行时间,还包括了ADI的GPIO中断处理函数的命令执行时间。

当MCU启动时,CPU会运行启动文件(即startup.s)。所有事件的处理函数均在该文件中定义,包括GPIO中断处理函数。一旦触发GPIO中断,CPU就会执行中断处理函数(即ADI的GPIO驱动程序中的GPIO_A_INT_HANDLER和GPIO_B_INT_HANDLER)。通用的中断处理函数会在所有的GPIO引脚中搜索触发中断的引脚并清零其中断状态、运行回调函数。由于DRDY是本文应用的唯一中断信号,因此可以对函数进行简化以加快进程。可选的解决方案包括 (1)在启动文件中重新定位目标,以及(2)修改原始的中断处理函数。重新定位目标意味着自定义中断处理函数,并替换启动文件中的原始的中断处理函数。

而修改原始的中断处理函数只需要添加一个自定义的GPIO驱动程序。本文采用第二种方案修改原始的中断处理函数,如图8所示。该方案只将连接至DRDY的GPIO的引脚中断状态清零,并直接转到回调函数。请注意,这里需要通过取消选择build target中关于原始GPIO驱动函数的勾选框内容来隔离原始的GPIO驱动程序。

12.png

图8. 嵌套矢量中断控制器(NVIC)。

结果

速率性能

假定现在需要读取200个24位的ADC输出数据,并且SPI位速率设置为13 MHz。将DRDY信号和SCLK信号的引脚连接至示波器,可以通过观察DRDY信号与SPI数据事务处理(亦即DMA事务处理)启动之间的时间间隔的方法可以量化本文所述的每种方法对速率的改善程度。这里将DRDY信号至SCLK信号开始的时间间隔记为∆t,那么对于13 MHz的SPI速率,测量得出的∆t为:

  • (a)基本模式DMA Δt = 3.754 μs 

  • (b)乒乓模式DMA Δt = 2.8433 μs 

  • (c)乒乓模式DMA(使用优化的中断处理函数)Δt = 1.694 μs 

方法(a)和(b)可支持64 kHz的ODR,而方法(c)可支持128 kHz的ODR。这是因为方法(c)的∆t最短,从而使得SCLK信号能够更早结束。如果 SCLK信号(即数据事务处理)能在T/2之前完成(T为当前ADC的数据输出周期),则ODR可实现翻倍。这较之于原始的ADISPI驱动程序可以达到的8 kHz的ODR性能是一次巨大的进步。

13.png

图9. (a)基本模式DMA、(b)乒乓模式以及(c)乒乓模式(使用优化的中断处理函数)的Δt。

使用MAX32660控制AD7768-1

使用主时钟速率为96 MHz的MCU MAX32660控制AD7768-1)时的结果如何?在该情况下,使用优化的中断处理函数的中断设置,可在不使用DMA函数的情况下实现256 kHz的数据输出速率。参见图10。

14.png

图10. 不使用DMA时MAX32660的ODR。

结论

本文利用选定的ADC(AD7768-1)和MCU(ADuCM4050或MAX32660)通过 SPI实现了高速的数据事务处理。为实现速率优化的目标,本文简化了ADI的SPI驱动程序执行数据事务处理。此外本文提出,激活DMA控制器释放内核也可以加快连续数据事务处理的流程。在 DMA的乒乓模式下,DMA的配置时间可通过适当的调度来节省。在此基础上,还可以通过直接指定中断引脚的方式优化中断处理函数。在13 MHz的SPI位速率下,本文提出的方案的最佳性能可达到128 kSPS的ADC ODR。

表5. 使用ADuCM405和MAX32660实现的高速SPI连接

15.png

来源:ADI智库

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。CW32L083 内部集成 2 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为 主机或从机,支持多主机通信模式,支持直接内存访问(DMA)。

一、主要功能

• 支持主机模式、从机模式 

• 支持全双工、单线半双工、单工 

• 可选的 4 位到 16 位数据帧宽度 

• 支持收发数据 LSB 或 MSB 在前 

• 可编程时钟极性和时钟相位

• 主机模式下通信速率高达 PCLK/2 

• 从机模式下通信速率高达 PCLK/4 

• 支持多机通信模式 

• 8 个带标志位的中断源 

• 支持直接内存访问 (DMA)

1.功能框图

1.png

SPI 一般通过 4 个引脚与外部设备相连:

• MOSI 主机输出 / 从机输入,用于主机模式下的数据发送和从机模式下的数据接收; 

• MISO 主机输入 / 从机输出,用于主机模式下的数据接收和从机模式下的数据发送; 

• SCK 同步串行时钟,主机时钟输出和从机时钟输入,发送或接收主机同步时钟; 

• CS 从机选择,也用于多机通信时总线冲突检测

CW32L083 支持用户灵活选择 GPIO 作为 SPI 通信引脚,通过 AFR 功能复用实现,具体 SPI 引脚请参考数据手册中的引脚定义。

2.全双工模式

SPI 支持全双工通信模式,在该模式下,主机和从机的移位寄存器通过 MOSI、MISO 两条单向数据线进行连接, 在主机提供的 SCK 时钟信号的控制下同时进行两个方向的数据传输。设置控制寄存器 SPIx_CR1 的 MODE 位域为 0x0,使 SPI 工作于全双工通信模式。

2.png

二、FLASH存储器及电路连接

3.png

W25Q64JVSSIQ为华邦NOR型FLASH,存储容量为8Mbytes,每页256bytes,总共有32768个可编程页,最大一次可编程大小为256bytes。一次擦除大小可以为4K、32K、64K字节(K=1024)或者全擦除。支持标准SPI、Dual SPI、Quad SPI,最大操作时钟为133MHz。

芯片SPI接口时序要求如下:

4.png

由接口时序图可知,空闲电平可高可低,数据在第1个时钟上升沿采样。对应的可以选择用模式0或者模式3和MCU进行对接。

三、实例演示

本实例演示MCU(CW32L083)芯片对外部SPI FLASH(W25Q64JVSSIQ)进行读写访问,SPI接口选择模式0。

1.配置RCC和GPIO

void RCC_Configuration(void)
{
  RCC_HSI_Enable(RCC_HSIOSC_DIV2); //SYSCLK = HSI = 24MHz = HCLK = PCLK
  //外设时钟使能
  RCC_AHBPeriphClk_Enable(DEBUG_UART_GPIO_CLK | RCC_AHB_PERIPH_GPIOC, ENABLE);
  DEBUG_UART_APBClkENx(DEBUG_UART_CLK, ENABLE);
}
             
void GPIO_Configuration(void)//包含UART TX RX 复用和LED开关
{
  GPIO_InitTypeDef GPIO_InitStructure = {0};
  DEBUG_UART_AFTX; //UART TX RX 复用
  DEBUG_UART_AFRX;
  GPIO_InitStructure.Pins = DEBUG_UART_TX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = DEBUG_UART_RX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = GPIO_PIN_3 | GPIO_PIN_2; //PC3 LED1 / PC2 LED2
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
  PC03_SETLOW();//LED灭
  PC02_SETLOW();
}

2.配置UART 

void UART_Configuration(void)
{
   UART_InitTypeDef UART_InitStructure = {0};
   UART_InitStructure.UART_BaudRate = DEBUG_UART_BaudRate; // 波特率
   UART_InitStructure.UART_Over = UART_Over_16; // 采样方式
   UART_InitStructure.UART_Source = UART_Source_PCLK; // 传输时钟源UCLK
   UART_InitStructure.UART_UclkFreq = DEBUG_UART_UclkFreq; // 传输时钟UCLK频率
   UART_InitStructure.UART_StartBit = UART_StartBit_FE; // 起始位判定方式
   UART_InitStructure.UART_StopBits = UART_StopBits_1; // 停止位长度
   UART_InitStructure.UART_Parity = UART_Parity_No ; // 校验方式
   UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None; 
   //硬件流控
   UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; // 发送/接收使能
   UART_Init(DEBUG_UARTx, &UART_InitStructure);
}

3.配置SPI接口

void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  /************************GPIO Configuration***********************/
  RCC_AHBPeriphClk_Enable(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK | FLASH_SPI_CS_GPIO_CLK, ENABLE);
  FLASH_SPI_APBClkENx(FLASH_SPI_CLK, ENABLE);
  //SPI SCK MOSI MISO 复用
  FLASH_SPI_AF_SCK;
  FLASH_SPI_AF_MISO;  
  FLASH_SPI_AF_MOSI;     
  //CS
  GPIO_InitStructure.Pins = FLASH_SPI_CS_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
  //SCK   
  GPIO_InitStructure.Pins = FLASH_SPI_SCK_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
  //MOSI
  GPIO_InitStructure.Pins = FLASH_SPI_MOSI_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
  //MISO
  GPIO_InitStructure.Pins = FLASH_SPI_MISO_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
   //拉高CS
  FLASH_SPI_CS_HIGH();
  /************************SPI Configuration***********************/
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    // 双线全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;              // 主机模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 帧数据长度为8bit
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;       // 时钟空闲电平为低
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;     // 第1边沿采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        // 片选信号由SSI寄存器控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; 
  // 波特率为PCLK的8分频
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    // 最高有效位 MSB 收发在前
  SPI_InitStructure.SPI_Speed = SPI_Speed_High;    // 低速SPI
  SPI_Init(FLASH_SPIx, &SPI_InitStructure);
}

4.比较两个缓冲区数据

TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
     if(*pBuffer1 != *pBuffer2)
     {
       return FAILED;                 
     }
     pBuffer1++;
     pBuffer2++;               
  }
  return PASSED;
}

5.将C库printf函数重新定位到UART

PUTCHAR_PROTOTYPE
{
   UART_SendData_8bit(DEBUG_UARTx, (uint8_t)ch);// 通过UARTx发送一个数据(8bit)
   while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
   return ch;
}
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
   size_t nChars = 0;     
                 
   if (buffer == 0)
   {//应该刷新内部缓冲区 "handle" == -1
     return 0;
   }
   for (size != 0; --size)
   {
     UART_SendData_8bit(DEBUG_UARTx, *buffer++);
     while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
     ++nChars;
   }
   return nChars;
}

6.主程序实现SPI对FLASH的读写(具体对FLASH读写可参考cw32_eval_spi_flash.c)

int32_t main(void)
{ 
  RCC_Configuration();//配置RCC
  GPIO_Configuration();//配置GPIO
  UART_Configuration();//配置UART
  SPI_FLASH_Init();//配置SPI
  printf("\r\nCW32L083 SPI FLASH Example\r\n");
  DeviceID = SPI_FLASH_DeviceID();//读取Device ID
  ManufactDeviceID = SPI_FLASH_ManufactDeviceID();//读取Manufacturer / Device ID
  JedecID = SPI_FLASH_JedecID();//读取JEDEC ID
  SPI_FLASH_UniqueID(UniqueID);// 读取Unique ID (64bit)
  printf("\r\nDeviceID = 0x%X\r\nManufactDeviceID = 0x%X\r\nJedecID = 0x%X", DeviceID, ManufactDeviceID, JedecID);
  printf("\r\nUniqueID = 0x ");
  for(uint8_t i = 0; i<8; i++)
  {
     printf("%X ", UniqueID[i]);
  }
  
  if(JedecID == sJedecID)  /* Check JEDEC ID */
  {
     printf("\r\n\nFLASH Detected\r\n");;
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferWrite(TxBuffer, FLASH_WriteAddress, BufferSize); //写数据
     printf("\r\n写入的数据为:%s\r\n", TxBuffer);
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     printf("\r\n读出的数据为:%s\r\n", RxBuffer);
     //检查写入的数据与读出的数据是否一致
     TransferStatus = Buffercmp(TxBuffer, RxBuffer, BufferSize);
     if(TransferStatus == PASSED)
     {
        PC03_SETHIGH();
        printf("\r\nFLASH Success\r\n");
     }
     else
     {
        PC02_SETHIGH();
        printf("\r\nFLASH Error 1\r\n");
     }
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     for(uint8_t j = 0; j < BufferSize; j++)
     {
        if(RxBuffer[j] != 0xFF)
        {
           PC02_SETHIGH();
           printf("\r\nFLASH Error 2\r\n");
        }
     }                
   }
   else// (FlashID != sFLASH_ID)
   {
     PC02_SETHIGH();
     printf("\r\nFLASH Error 3\r\n");
   }
   while(1)
   {
   }
}

7.演示说明

程序读外部FLASH的DeviceID、ManufactDeviceID、JedecID、UniqueID并打印读取到的芯片相关信息,然后依次进行写入数据、读取数据、比较数据、擦除SECTOR、读取数据等操作,当读写正确时,通过UART打印“FLASH Success”,LED1亮。当读取的JedecID不正确,写入读取内容不一致,擦除后读取数据不为全FF时,打印“FLASH Error 2”,LED2亮。如下图所示:

5.png

关于CW32更多详细信息,请访问官方网站www.whxy.com。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 227

页面

订阅 RSS - SPI