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
硬件连接:
- 在Keil中打开 NXP 官网下载的 SDK 中的例程中的 SDK_2.6.0_LPCXpresso824MAX\boards\lpcxpresso824max\driver_examples\spi\transfer_dma\master 工程。
- 简单分析一个 SPI 主机的代码架构。
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 */
}
- 简单分析一个 SPI 从机的代码架构。
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 */
}
- 将主从机代码分别烧录到两块 LPC82x,按照硬件连接图示接线。
- 连接两块 LPC82x 到 PC 串口助手,波特率 9600,先启动从机,再启动主机。
- 主机收到如下打印,SPI 收发成功。
7、从机收到如下打印,SPI 收发成功。
评论