【CS32L015C8T6】SPI详解及硬件SPI配置方法(内附完整代码及注释)
目录
五、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~
前言:
长颈鹿最近在使用芯海的CS32L015C8T6单片机进行工程开发,发现网上有关这款单片机的资料非常少,而且官方提供的驱动代码有些不适用,因此决定记录一下造轮子的过程,前车之鉴,后车之师,希望能够帮助后来的开发者减少一些重复性造轮子的工作,少走一些弯路。
一、SPI简介
SPI(Serial Peripheral Interface,串行外设接口)是一种 全双工 的 同步 串行 通信协议,由摩托罗拉公司设计定义,具有四个功能引脚,CS(片选引脚)、CLK(同步时钟信号引脚)、MISO(主机输入从机输出引脚)、MOSI(主机输出从机输入引脚),SPI的协议时序逻辑和硬件实现简单清晰,是嵌入式系统中最常用的通信协议之一。
SPI协议非常适用于高速数据交互场景,它传输速度相比UART、IIC等协议更快,现在很多外部FLASH闪存芯片都是用SPI来进行数据通信,例如W25Qx系列的FLASH芯片,使用SPI协议的扩展版QSPI(修改FLM下载算法以及地址映射)作为通信协议时,外部FLASH读写速度(200MB/s)可以媲美甚至超过单片机内部FLASH(100MB/s),外挂FLASH可以直接当内部FLASH使用,为单片机开发提供了更大的内存空间,可以实现更丰富的功能。
二、SPI时序介绍
如图所示,左侧CS就是片选信号(有的地方也叫NSS)、SPC对应时钟信号SCL、SDI对应主机输入引脚MISO、SDO对应主机输出引脚MOSI。
图中的红色方框指的是主机发送的1字节命令,命令的构成是1位RW(读写控制位)+7位AD(寄存器地址),当然,命令都是由芯片厂商定义好的,不同的命令可以通过查询芯片数据手册来获得。DI对应的是主机的输入引脚,即MISO,DO对应的是主机的输出引脚,即MOSI。
SPI的时序非常简洁,CS控制芯片通信使能,CS拉低,表示主机可以和从机进行通信,SPC时钟信号一直稳定输出就可以,发送的数据按字节加载到移位寄存器,该发送发送,该接收接收,非常简单。
特别补充一点——SPI通信的底层逻辑实现,建议不清楚的同学们了解一下:
SPI通信是通过硬件8位数据移位寄存器进行数据传输的,主机发送数据时,先把一个字节的数据放入发送缓冲区,缓冲区数据会被自动加载到8位移位寄存器,然后按位进行发送,首先将字节数据最高位对应的电信号输出到主机的MOSI输出引脚,与此同时,从机的MISO输入引脚接收到主机发来的数据位电信号,将该位电信号传入从机设备的8位移位寄存器中,同时从机将8位移位寄存器的最高位也转换为电信号通过从机输出管脚输出值主机MISO,主机接收该位电信号到移位寄存器,至此主机从机完成了一个数据位的交换,接下来主&从机移位寄存器同时进行移位,准备下一个数据位的发送接收。以上步骤重复8次,就完成了一个字节数据的传输。
相信聪明的你已经发现,SPI协议本质上就是主机从机的移位寄存器之间一字节数据的交换,本质是交换,这一点需要记住。
三、CS32L015单片机SPI硬件资源简介
需要特别说明的是,芯海的CS32L015单片机在使用硬件SPI发送时,即使不开启SPI中断,也会触发系统的SPI中断请求,这是一个BUG,如果没有做处理,会导致系统陷入B. 异常中断中,Chipsea.CS32L015 DFP.1.0.2软件包中没有修复这个BUG,在每次发送后需要开发者手动关闭中断,清空SPI中断标志位,但Chipsea.CS32L015 DFP.1.0.7版本的pack包修复了这个BUG,这里举一个Chipsea.CS32L015 DFP.1.0.7版本的pack包的SPI发送库函数示例:

可以看到,在进入SPI发送库函数后,首先调用__SPI_FORCE_DISABLE_ISR();函数强制关闭复位了SPI的中断,这是芯海芯片关于SPI很特殊的一点,如果你的pack包版本低于1.0.7,就需要特别注意一下这个现象。
接下来介绍CS32L015C8T6单片机的SPI硬件资源情况,这款单片机有两个SPI接口(SPI0,SPI1),支持配置主/从机,通信模式可选全双工模式和简单模式,最大通信速度12Mbit/s,芯片还实现了硬件CRC功能(循环冗余校验),用来检验数据传输的正确与否。开启CRC功能够,如果CRC校验不通过,会立刻触发SPI错误回传机制,这样可以弥补SPI没有IIC、TCP/IP协议那样的通信应答机制的缺陷,提高SPI数据通信的稳定性。
长颈鹿目前还没有使用过CRC循环冗余校验功能,因为开发的产品使用场景内电磁情况并不复杂,SPI通信可以说是很稳定的,但我也意识到,在一些数据重要的场合,特别是往FLASH中存储系统关键运行参数/代码时,需要考虑到比特跳变数据异常现象的处理方式,CRC循环冗余校验,要比把存入的数据读取出来再挨个对比要快不少。
四、硬件SPI配置完整代码及注释
1、初始化GPIO
/*****************************************************************************
[函数名称]MX_SPI_MspInit
[函数功能]SPI底层外设初始化
[参 数]
[备 注]
*****************************************************************************/
void MX_SPI_MspInit()
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIOx = {0};
GPIOx.Mode = GPIO_MODE_AF;
GPIOx.OpenDrain = GPIO_PUSHPULL;
GPIOx.Debounce.Enable = GPIO_DEBOUNCE_DISABLE;
GPIOx.SlewRate = GPIO_SLEW_RATE_HIGH;
GPIOx.DrvStrength = GPIO_DRV_STRENGTH_HIGH;
GPIOx.Pull = GPIO_NOPULL;
//初始化第一个SPI的GPIO管脚
GPIOx.Pin = SPI0_CS_Pin;
GPIOx.Alternate = GPIO_AF_MODE_1; //SPI片选复用功能
HAL_GPIO_Init( SPI0_CS_GPIO_Port, &GPIOx ); //初始化片选引脚
GPIOx.Pin = SPI0_SCK_Pin;
GPIOx.Alternate = GPIO_AF_MODE_1; //SPI时钟复用功能
HAL_GPIO_Init( SPI0_SCK_GPIO_Port, &GPIOx ); //初始化片选引脚
GPIOx.Pin = SPI0_MISO_Pin;
GPIOx.Alternate = GPIO_AF_MODE_1; //SPI MISO复用功能
HAL_GPIO_Init( SPI0_MISO_GPIO_Port, &GPIOx ); //初始化片选引脚
GPIOx.Pin = SPI0_MOSI_Pin;
GPIOx.Alternate = GPIO_AF_MODE_1; //SPI MOSI复用功能
HAL_GPIO_Init( SPI0_MOSI_GPIO_Port, &GPIOx ); //初始化片选引脚
}
2、初始化SPI
/*****************************************************************************
[函数名称]MX_SPI_Init
[函数功能]SPI初始化
[参 数]
[备 注]
*****************************************************************************/
void MX_SPI_Init()
{
//初始化第一个SPI0
MX_SPI_MspInit(); //SPI的GPIO管脚初始化
__HAL_RCC_SPI0_CLK_ENABLE(); //开启SPI0对应的RCC时钟
hspi0.Instance = SPI0;
hspi0.Init.Mode = HAL_SPI_MODE_MASTER; //配置为主机模式
hspi0.Init.NSS = HAL_SPI_NSS_LOW; //NSS片选端低电平有效
hspi0.Init.CLKPhase = HAL_SPI_PHASE_2EDGE; //一周期内,第一个下降沿进行数据采样
hspi0.Init.CLKPolarity = HAL_SPI_POLARITY_LOW; //极性,空闲时钟低电平
hspi0.Init.BaudRatePrescaler = HAL_SPI_PCLK_PRESCALER_8; //时钟预分频
if( HAL_SPI_Init( &hspi0 ) != HAL_OK)
{
while(1);
}
}
3、SPI数据发送接收函数说明
//发送
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *pHSpi, uint8_t *pData, uint16_t Size, uint32_t Timeout_ms);
//接收
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *pHSpi, uint8_t *pData, uint16_t *pSize, uint32_t Timeout_ms);
//发送&接收
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *pHSpi,
uint8_t *pTxData, uint8_t *pRxData,
uint16_t Size, uint32_t Timeout_ms);
通过这三个芯海封装好的HAL库函数进行SPI的数据发送和接收,当配置完成后,用户可以调用这三个函数进行SPI通信,他们之间有什么不同呢?要想弄明白这三个函数之间的异同点,就首先需要理解SPI的数据传输方式。
SPI通信是通过硬件8位数据移位寄存器进行数据传输的,主机发送数据时,先把一个字节的数据放入发送缓冲区,缓冲区数据会被自动加载到8位移位寄存器,然后按位进行发送,首先将字节数据最高位对应的电信号输出到主机的MOSI输出引脚,与此同时,从机的MISO输入引脚接收到主机发来的数据位电信号,将该位电信号传入从机设备的8位移位寄存器中,同时从机将8位移位寄存器的最高位也转换为电信号通过从机输出管脚输出值主机MISO,主机接收该位电信号到移位寄存器,至此主机从机完成了一个数据位的交换,接下来主&从机移位寄存器同时进行移位,准备下一个数据位的发送接收。以上步骤重复8次,就完成了一个字节数据的传输。SPI协议本质上就是主机从机的移位寄存器之间一字节数据的交换,本质是交换,那么这三个函数的功能就好理解了:
HAL_SPI_Transmit();
主机发送函数,主机从机移位寄存器数据交换,但主机忽略掉交换到的从机数据(无意义)
HAL_SPI_Receive();
主机接收函数,主机从机移位寄存器数据交换,但主机一般会往移位寄存器中写入0xFF无效数据,从而让从机不执行任何操作(命令无意义)。
HAL_SPI_TransmitReceive();
主机发送接收函数,主机从机移位寄存器数据交换,两者的数据都不忽略。
五、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~
更多推荐



所有评论(0)