你要的全拿走,剩下的我来写 - LPC82x 串行外设接口

一、性能概述

1、概述

      SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 NXP LPC82x 家族。

  • LPC82x 家族所有型号都提供了 SPI 接口,且包含 2 个 SPI 控制器。
  • SPI 功能可以通过开关矩阵(Switch Matrix)来配置安排到所有数字引脚。

      关于开关矩阵(Switch Matrix)功能的详细介绍,大家可以查阅往期文章《原来“明星产品”是这样称霸市场的 – LPC82x 特色功能开关矩阵》了解更多哦。

      点击任意门直接传送:https://mp.weixin.qq.com/s/G_nP4xjKOBxp-tzQHAE4MQ

2、特性

      小伙伴们肯定想快速清楚 LPC82x 系列的 SPI 特性,下面就给大家总结成下面 7 点:

     A. 1-16 位的数据帧被直接支持,更大的数据帧可结合软件来实现。

     B. 支持主 / 从模式。

     C. 支持发送数据到从设备时,无需读取进来的数据。这可用于外接 SPI 存储器时。

     D. 可以选择将控制信息随着数据一起写入。这将允许实现多种功能的操作,包括任意长度的数据帧的操作。

     E. 最多 4 条片选输入输出信号,且极性可选,使用灵活。

     F. 收发支持 DMA。

     G. 支持低功耗模式(sleep / deep sleep / power down mode)唤醒。

特别注意:LPC82x SPI 不支持 TI 的 SSI 和 National 的 Microwire 模式哦。

重要提醒:引脚 PIO0_10 和 PIO0_11(open-drain pins)为原生开漏模式,更适应 I2C 不同速率,用户在设计应用时,尽量用 PIO0_10 和 PIO0_11 作为 I2C 管脚,不要用于 SPI !!


3. 功能框图


     

二、SPI 具体应用操作

1. 时钟产生

  • LPC82x 在从模式下,SPI 的时钟信号 SCK 由外部主设备提供。
  • LPC82x 在主模式下,SPI 的时钟信号来自系统时钟,可以通过 SPI 的时钟分频器产生不同的时钟频率(= PCLK_SPIn / DIVVAL)。
  • 时钟产生框图如下:

     
2. 片选信号配置

     SPI 模块提供了 4 个片选信号,且每个片选信号极性都可配置(高或低有效)。

     需要注意的是并不是所有 SPI 都提供 4 个片选信号:

     SPI0 提供 4 个片选信号 SSEL[3:0]

     SPI1 提供 2 个片选信号 SSEL[1:0]

 

主模式:

  • 片选信号为输出
  • 所有连接到引脚上的片选信号 SSEL 都将按照寄存器里定义的值输出。
  • 数据通讯时,至少有一个片选信号 SSEL 是有效的。
  • 片选信号由 TXCTL 和 TXDATCTL 寄存器配置。
     

从模式:

  • 片选信号为输入
  • 任何连接到某个引脚的片选信号 SSEL 有效都将激活 SPI。
  • 片选信号的状态随着接收到的数据一起保存在 RXDAT 寄存器中。

     

3. 基本操作模式

    SPI 时钟信号的极性(CPOL)和相位(CPHA)均可配置,一共可配置出 4 种基本操作模式。

     

     
      

4. 数据通讯速率(位率)

     A. 主模式:

          受限于系统时钟频率,最大位率可达 30 Mbit/s。

     B. 从模式:

          通讯速率(位率)和供电电压 VDD,由外部设备和 PCB 走线引入的延时有关。

          若不考虑外部设备和 PCB 走线引入的延时,则(非开漏引脚)

          当 3.0 V <= VDD <= 3.6 V,最大位率可达约 18 Mbit/s。

          当 1.8 V <= VDD < 3.0 V,最大位率可达约 14 Mbit/s。

5. 如何配置 SPI 低功耗模式唤醒

     LPC82x 系列拥有两种低功耗 SPI 模式,分别是

          1. 睡眠模式

          2. 深度睡眠模式/掉电模式

 

     按照下图配置即可进入睡眠模式:

     

     还有进入深度睡眠模式/掉电模式:

     

三、动手实验

4.1、 SPI 主/从模式下数据收发(DMA 方式)

     下面我们通过一个基础的实验来更真实地理解和掌握 LPC82x 系列的 SPI(DMA 方式)接口。

     实验目的:

  • 工作于主模式或从模式时,如何对其进行配置。
  • 主模式或从模式下,如何进行数据收发。
  • 如何使用 DMA 方式实现 SPI 收发数据

 

     实验描述:

     本实验实现 SPI 主模式和从模式的数据收发通讯(DMA 方式)。

     一块 LPC82x SPI0 为主机,另一块 LPC82x 的 SPI0 为从机。

     主机从机互相发送数据,并打印出来。

 

     硬件环境:

     硬件:LPC82x Xpresso v2/mbed

 

     硬件连接:


     
  1. 在Keil中打开 NXP 官网下载的 SDK 中的例程中的 SDK_2.6.0_LPCXpresso824MAX\boards\lpcxpresso824max\driver_examples\spi\transfer_dma\master 工程。

  1. 简单分析一个 SPI 主机的代码架构。
     (1). 初始化 SPI0 时钟

          static inline void CLOCK_EnableClock(clock_ip_name_t clk)

          {

              SYSCON->SYSAHBCLKCTRL |= 1 << CLK_GATE_GET_BITS_SHIFT(clk);

          }

      (2). 初始化 SPI0 引脚

          const uint32_t pio24_config = (       /* Selects pull-up function */

                                   IOCON_PIO_MODE_PULLUP |

                                   /* Enable hysteresis */

                                   IOCON_PIO_HYS_EN |

                                   /* Input not invert */

                                   IOCON_PIO_INV_DI |

                                   /* Disables Open-drain function */

                                   IOCON_PIO_OD_DI |

                                   /* Bypass input filter */

                                   IOCON_PIO_SMODE_BYPASS |

                                   /* IOCONCLKDIV0 */

                                   IOCON_PIO_CLKDIV0);

          /* PORT2 PIN4 (coords: ) is configured as  */

          IOCON_PinMuxSet(IOCON, 24, pio24_config);

 

          const uint32_t pio22_config = (       /* Selects pull-up function */

                                         IOCON_PIO_MODE_PULLUP |

                                         /* Enable hysteresis */

                                         IOCON_PIO_HYS_EN |

                                         /* Input not invert */

                                         IOCON_PIO_INV_DI |

                                         /* Disables Open-drain function */

                                          IOCON_PIO_OD_DI |

                                          /* Bypass input filter */

                                         IOCON_PIO_SMODE_BYPASS |

                                         /* IOCONCLKDIV0 */

                                         IOCON_PIO_CLKDIV0);

          /* PORT2 PIN2 (coords: ) is configured as  */

          IOCON_PinMuxSet(IOCON, 22, pio22_config);

 

          const uint32_t pio23_config = (       /* Selects pull-up function */

                                         IOCON_PIO_MODE_PULLUP |

                                         /* Enable hysteresis */

                                         IOCON_PIO_HYS_EN |

                                         /* Input not invert */

                                         IOCON_PIO_INV_DI |

                                         /* Disables Open-drain function */

                                         IOCON_PIO_OD_DI |

                                         /* Bypass input filter */

                                         IOCON_PIO_SMODE_BYPASS |

                                         /* IOCONCLKDIV0 */

                                         IOCON_PIO_CLKDIV0);

          /* PORT2 PIN3 (coords: ) is configured as  */

          IOCON_PinMuxSet(IOCON, 23, pio23_config);

 

          const uint32_t pio10_config = (       /* Selects pull-up function */

                                         IOCON_PIO_MODE_PULLUP |

                                         /* Enable hysteresis */

                                         IOCON_PIO_HYS_EN |

                                         /* Input not invert */

                                         IOCON_PIO_INV_DI |

                                         /* Disables Open-drain function */

                                         IOCON_PIO_OD_DI |

                                         /* Bypass input filter */

                                         IOCON_PIO_SMODE_BYPASS |

                                         /* IOCONCLKDIV0 */

                                         IOCON_PIO_CLKDIV0);

          /* PORT1 PIN0 (coords: ) is configured as  */

          IOCON_PinMuxSet(IOCON, 10, pio10_config);

 

          /* SPI0_SCK connect to P0_24 */

          SWM_SetMovablePinSelect(SWM0, kSWM_SPI0_SCK, kSWM_PortPin_P0_24);

 

          /* SPI0_MOSI connect to P0_26 */

          SWM_SetMovablePinSelect(SWM0, kSWM_SPI0_MOSI, kSWM_PortPin_P0_26);

 

          /* SPI0_MISO connect to P0_25 */

          SWM_SetMovablePinSelect(SWM0, kSWM_SPI0_MISO, kSWM_PortPin_P0_25);

 

          /* SPI0_SSEL0 connect to P0_15 */

          SWM_SetMovablePinSelect(SWM0, kSWM_SPI0_SSEL0, kSWM_PortPin_P0_15);

 

      Ps. 这里可能有人会觉得疑惑,明明要配置 PIO24,PIO26,PIO25,PIO15,为啥变成了 pio10,pio15,pio22,pio23?

      因为 LPC82x 系列引脚有自己的映射表,请参照映射表配置哦。

      #define    IOCON_INDEX_PIO0_24       (24)

      #define    IOCON_INDEX_PIO0_26       (22)

      #define    IOCON_INDEX_PIO0_25       (23)

      #define    IOCON_INDEX_PIO0_15       (10)




      (3)、初始化 SPI 主模式

      ① 配置主机的波特率(速率/位率)

          masterConfig.baudRate_Bps = EXAMPLE_SPI_MASTER_BAUDRATE;

      ② 选择片选信号

          masterConfig.sselNumber   = EXAMPLE_SPI_MASTER_SSEL;

      ③ 配置使用的时钟源频率

          srcFreq                   = EXAMPLE_SPI_MASTER_CLK_FREQ;

 
      (4). 配置主机 DMA

      ① 初始化 DMA 模块

          /* DMA init */

          DMA_Init(EXAMPLE_SPI_MASTER_DMA_BASEADDR);

      ② 使能 RX DMA 通道,创建事件处理结构以及注册回调函数

          /* Enable channel and Create handle for RX channel. */

          DMA_EnableChannel(EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_RX_CHANNEL);

          DMA_CreateHandle(&masterRxHandle, EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_RX_CHANNEL);

          DMA_SetCallback(&masterRxHandle, SPI_DmaRxCallback, NULL);

      ③ 使能 TX DMA 通道,创建事件处理结构以及注册回调函数

           /* Enable channel and Create handle for TX channel. */

          DMA_EnableChannel(EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_TX_CHANNEL);

          DMA_CreateHandle(&masterTxHandle, EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_TX_CHANNEL);

          DMA_SetCallback(&masterTxHandle, SPI_DmaTxCallback, NULL);

      ④ 设置通道优先级

          /* Set the channel priority. */

          DMA_SetChannelPriority(EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_TX_CHANNEL, kDMA_ChannelPriority3);

          DMA_SetChannelPriority(EXAMPLE_SPI_MASTER_DMA_BASEADDR, EXAMPLE_SPI_MASTER_RX_CHANNEL, kDMA_ChannelPriority2);

 
      (5). 开始 DMA 传输数据到从机

      ① 准备收发 buffer 数据

          /* Prepare buffer to send and receive data. */

          for (i = 0U; i < BUFFER_SIZE; i++)

           {

              txBuffer[i] = i;

              rxBuffer[i] = 0U;

          }

      ② 配置 DMA RX buff 大小和指针位置,并提交 DMA 接收请求

          /* Prepare and start DMA RX transfer. */

          DMA_PrepareTransfer(&masterRxDmaConfig, (void *)&EXAMPLE_SPI_MASTER->RXDAT, rxBuffer, sizeof(uint8_t), BUFFER_SIZE,

                              kDMA_PeripheralToMemory, NULL);

          DMA_SubmitTransfer(&masterRxHandle, &masterRxDmaConfig);

      ③ 开始 DMA RX 接收

          /* Start DMA TX transfer. */

          DMA_StartTransfer(&masterRxHandle);

      ④ 配置 DMA 发送设置

          /* DMA transfer configuration setting. */

          dma_xfercfg_t tmp_xfercfg = {0};

          tmp_xfercfg.valid         = true;

          tmp_xfercfg.swtrig        = true;

          tmp_xfercfg.intA          = true;

          tmp_xfercfg.byteWidth     = sizeof(uint32_t);

          tmp_xfercfg.srcInc        = 0;

          tmp_xfercfg.dstInc        = 0;

          tmp_xfercfg.transferCount = 1;

      ⑤ 创建链式描述符来传输最后一个数据,并将 DMA 发送配置参数放入描述符中

          /* Create chained descriptor to transmit last word */

          DMA_CreateDescriptor(&txDescriptor, &tmp_xfercfg, &lastData, (void *)&EXAMPLE_SPI_MASTER->TXDATCTL, NULL);

 

          /* Add confifuration parameter to descriptor. */

          DMA_PrepareTransfer(&masterTxDmaConfig, txBuffer, (void *)&EXAMPLE_SPI_MASTER->TXDAT, sizeof(uint8_t),

                        BUFFER_SIZE - 1, kDMA_MemoryToPeripheral, &txDescriptor);

      ⑥ 在发送第一个描述符的时候关掉中断避免重复调用回调函数,并提交 DMA 发送请求

          /* Disable interrupts for first descriptor to avoid calling callback twice. */

          masterTxDmaConfig.xfercfg.intA = false;

          masterTxDmaConfig.xfercfg.intB = false;

 

          DMA_SubmitTransfer(&masterTxHandle, &masterTxDmaConfig);

      ⑦ 开始 DMA TX 发送

          /* Start DMA TX transfer. */

          DMA_StartTransfer(&masterTxHandle);

 
      (6). 打印并检查收到的数据是否正确

      (7). 关闭 DMA

      void DMA_Deinit(DMA_Type *base)

      {

          /* Disable DMA peripheral */

         base->CTRL &= ~(DMA_CTRL_ENABLE_MASK);

          #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)

          CLOCK_DisableClock(s_dmaClockName[DMA_GetInstance(base)]);

          #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

       }

 
      (8). 关闭 SPI

       void SPI_Deinit(SPI_Type *base)

       {

          /* Assert arguments */

          assert(NULL != base);

          uint32_t instance = SPI_GetInstance(base);

          /* Disable SPI module before shutting down the clock. */

          SPI_Enable(base, false);

 

       #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)

          /* Disable the clock. */

          CLOCK_DisableClock(s_spiClock[instance]);

       #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

       }

 

  1. 简单分析一个 SPI 从机的代码架构。
      (1) 初始化 SPI0 时钟

         static inline void CLOCK_EnableClock(clock_ip_name_t clk)

          {

              SYSCON->SYSAHBCLKCTRL |= 1 << CLK_GATE_GET_BITS_SHIFT(clk);

          }

 
      (2). 初始化 SPI0 引脚

          参照主机的 SPI0 引脚配置

      (3). 初始化 SPI 从模式

          static void EXAMPLE_SlaveInit(void)

          {

              spi_slave_config_t slaveConfig;

 

            /* Default configuration for slave:

           * userConfig.enableSlave = true;

           * userConfig.polarity = kSPI_ClockPolarityActiveHigh;

           * userConfig.phase = kSPI_ClockPhaseFirstEdge;

           * userConfig.direction = kSPI_MsbFirst;

           * userConfig.dataWidth = kSPI_Data8Bits;

           * userConfig.sselPol = kSPI_SpolActiveAllLow;

           */

          SPI_SlaveGetDefaultConfig(&slaveConfig);

            SPI_SlaveInit(EXAMPLE_SPI_SLAVE, &slaveConfig);

      }

 
      (4). 配置从机 DMA

          类似主机的配置方式

          static void EXAMPLE_SlaveDMASetup(void)

          {

          /* DMA init */

          DMA_Init(EXAMPLE_SPI_SLAVE_DMA_BASEADDR);

 

         /* Enable channel and Create handle for RX channel. */

          DMA_EnableChannel(EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_RX_CHANNEL);

          DMA_CreateHandle(&slaveRxHandle, EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_RX_CHANNEL);

          DMA_SetCallback(&slaveRxHandle, SPI_DmaRxCallback, NULL);

 

           /* Enable channel and Create handle for TX channel. */

          DMA_EnableChannel(EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_TX_CHANNEL);

          DMA_CreateHandle(&slaveTxHandle, EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_TX_CHANNEL);

          DMA_SetCallback(&slaveTxHandle, SPI_DmaTxCallback, NULL);

 

          /* Set the channel priority. */

          DMA_SetChannelPriority(EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_TX_CHANNEL, kDMA_ChannelPriority3);

          DMA_SetChannelPriority(EXAMPLE_SPI_SLAVE_DMA_BASEADDR, EXAMPLE_SPI_SLAVE_RX_CHANNEL, kDMA_ChannelPriority2);

}

      (5). 开始 DMA 传输数据到主机

      ① 准备发送 buff

          /* Init Buffer*/

          for (i = 0; i < BUFFER_SIZE; i++)

          {

              txBuffer[i] = i;

          }

      ② 配置 DMA RX buff 大小和指针位置,提交 DMA 接收请求,并开始接收数据

 

          /* Prepare and start DMA RX transfer. */

          DMA_PrepareTransfer(&slaveRxDmaConfig, (void *)&EXAMPLE_SPI_SLAVE->RXDAT, rxBuffer, sizeof(uint8_t), BUFFER_SIZE,

                        kDMA_PeripheralToMemory, NULL);

          DMA_SubmitTransfer(&slaveRxHandle, &slaveRxDmaConfig);

 

          DMA_StartTransfer(&slaveRxHandle);

 

      ③ 配置 DMA TX buff 大小和指针位置,提交 DMA 发送请求,并开始发送数据

          /* Prepare and start DMA TX transfer. */

          DMA_PrepareTransfer(&slaveTxDmaConfig, txBuffer, (void *)&EXAMPLE_SPI_SLAVE->TXDAT, sizeof(uint8_t), BUFFER_SIZE,

                        kDMA_MemoryToPeripheral, NULL);

          DMA_SubmitTransfer(&slaveTxHandle, &slaveTxDmaConfig);

 

        DMA_StartTransfer(&slaveTxHandle);


      (6). 打印并检查收到的数据是否正确

      (7). 关闭 DMA

        void DMA_Deinit(DMA_Type *base)

        {

            /* Disable DMA peripheral */

            base->CTRL &= ~(DMA_CTRL_ENABLE_MASK);

        #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)

          CLOCK_DisableClock(s_dmaClockName[DMA_GetInstance(base)]);

         #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

        }


      (8). 关闭 SPI

        void SPI_Deinit(SPI_Type *base)

        {

            /* Assert arguments */

            assert(NULL != base);

            uint32_t instance = SPI_GetInstance(base);

            /* Disable SPI module before shutting down the clock. */

            SPI_Enable(base, false);

 

         #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)

           /* Disable the clock. */

           CLOCK_DisableClock(s_spiClock[instance]);

        #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

        }

 

  1. 将主从机代码分别烧录到两块 LPC82x,按照硬件连接图示接线。
  1. 连接两块 LPC82x 到 PC 串口助手,波特率 9600,先启动从机,再启动主机。
  1. 主机收到如下打印,SPI 收发成功。
       

        7、从机收到如下打印,SPI 收发成功。

          

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论