SPI(二):主从通信
1、主从通信:主机轮询+从机中断
实现主从通信需要2块stm32单片机,一块用作主机,一块用作从机。
主机 从机
CLK:PA5(复用推挽输出)....................... PA5(浮空输入)
NSS:PB9(软件+通用推挽输出)...................PA4(硬件输入)
MOSI:PA7(复用推挽输出).......................PA7(浮空输入)
MISO:PA6(浮空输入)...........................PA6(复用推挽输出)
实验要求:按下主机按键(按键连接PB0),主机向从机发送数据,从机接受到数据后,也向主机发送数据。
主机的代码如下
①SPI.c文件的代码如下
#include "SPI.h"
/**
* @brief:SPI1初始化函数
*/
SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void)
{
/* SPI1的初始化 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_MASTER; //模式:主机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_SOFT; //GPIO控制NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
}
/**
* @brief:HAL_SPI_Init()调用此函数
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Init; //IO口初始化结构体
if(hspi->Instance == SPI1)
{
/* IO口的初始化:SPI1的CLK = PA5,MISO = PA6,MOSI = PA7,NSS = PB9*/
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP; //通用推挽输出
GPIO_Init.Pin = GPIO_PIN_9; //PB9
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOB, &GPIO_Init);
}else if(hspi->Instance == SPI2)
{
}
}
②SPI.h文件的代码如下
#ifndef __SPI_H
#define __SPI_H
#include "stm32f1xx_hal.h"
#define CSS_ENABLE HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET)//拉低NSS,开始时序
#define CSS_DISABLE HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET) //拉高SS,终止时序
extern SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void);
#endif
③main.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "SPI.h"
#include "UART.h"
#include "Key.h"
uint8_t WData[5] = {1,2,33,4,5};
uint8_t RData[5];
int main(void){
HAL_Init();
HSE_RCC_Init();
UART1_Init(115200);
SPI_Init();
Key_GPIO_Init();
printf("程序测试\r\n");
while(1){
switch(Key_Scan())
{
case 1:
printf("按键按下了,主机向从机发送数据!\r\n");
/* 发送数据 */
CSS_ENABLE;
HAL_SPI_TransmitReceive(&hspi1, WData, RData, 5, 1000);//发送数据
CSS_DISABLE;
for(uint8_t i = 0; i<sizeof(RData)/sizeof(RData[0]); i++)
{
printf("从机发送给主机的数据RData[%d] = %d\r\n",i,RData[i]);
}
break;
}
}
}
从机的代码如下
①SPI.c文件的代码如下
#include "SPI.h"
/**
* @brief:SPI1初始化函数
*/
SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void)
{
/* SPI1的初始化 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_SLAVE; //模式:从机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_HARD_INPUT; //NSS:硬件控制输入
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
/* 配置SPI1的NVIC */
HAL_NVIC_SetPriority(SPI1_IRQn,3,0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
}
/**
* @brief:HAL_SPI_Init()调用此函数
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Init; //IO口初始化结构体
if(hspi->Instance == SPI1)
{
/* IO口的初始化:SPI1的CLK = PA5,MISO = PA6,MOSI = PA7,NSS = 硬件控制 */
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入模式
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}else if(hspi->Instance == SPI2)
{
}
}
②SPI.h文件的代码如下
#ifndef __SPI_H
#define __SPI_H
#include "stm32f1xx_hal.h"
extern SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void);
#endif
③main.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "SPI.h"
#include "UART.h"
uint8_t WData[5] = {1,2,33,4,5};
uint8_t RData[5];
int main(void){
HAL_Init();
HSE_RCC_Init();
UART1_Init(115200);
SPI_Init();
printf("程序测试\r\n");
HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5);//开启接受发送数据,开启接受发送数据完成中断。
while(1){
}
}
④stm32f1xx_it.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "stm32f1xx_it.h"
#include "SPI.h"
#include "UART.h"
/**
* @brief:SPI1的中断服务函数
*/
void SPI1_IRQHandler(void)
{
HAL_SPI_IRQHandler(&hspi1);//中断服务总函数
}
/******************* 下面的中断的回调函数 ***************************/
/**
* @brief:SPI1收发数据完成的中断服务函数
*/
extern uint8_t RData[5];
extern uint8_t WData[5];
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
for(uint8_t i = 0; i<sizeof(RData)/sizeof(RData[0]); i++)
{
printf("主机发送给从机的数据RData[%d] = %d\r\n",i,RData[i]);
}
HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5);//开启接受发送数据,开启接受发送数据完成中断。
}else if(hspi->Instance == SPI2)
{
}
}
综上
:从机的NSS受主机的NSS控制,所以配置为SPI_NSS_HARD_INPUT(硬件控制输入),无需手动开关。
从机不会主动的接受和发送数据,从机中HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5)开启了接受发送数据+完成中断。当从机接受到主机发送来的数据的同时,才会把WData的数据发送给主机。当接受完成后(同时也发送完成)会进入中断回调函HAL_SPI_TxRxCpltCallback()。
2、主从通信:主机中断+从机DMA
主机的代码如下
①SPI.c文件的代码如下
#include "SPI.h"
/**
* @brief:SPI1初始化函数
*/
SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void)
{
/* SPI1的初始化 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_MASTER; //模式:主机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_SOFT; //GPIO控制NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
/* 配置SPI1的NVIC */
HAL_NVIC_SetPriority(SPI1_IRQn,3,0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
}
/**
* @brief:HAL_SPI_Init()调用此函数
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Init; //IO口初始化结构体
if(hspi->Instance == SPI1)
{
/* IO口的初始化:SPI1的CLK = PA5,MISO = PA6,MOSI = PA7,NSS = PB9*/
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP; //通用推挽输出
GPIO_Init.Pin = GPIO_PIN_9; //PB9
GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW; //最大输出速度:最低2MHz
HAL_GPIO_Init(GPIOB, &GPIO_Init);
}else if(hspi->Instance == SPI2)
{
}
}
②SPI.h文件的代码如下
#ifndef __SPI_H
#define __SPI_H
#include "stm32f1xx_hal.h"
#define CSS_ENABLE HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET)//拉低NSS,开始时序
#define CSS_DISABLE HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET) //拉高SS,终止时序
extern SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void);
#endif
③main.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "SPI.h"
#include "UART.h"
#include "Key.h"
uint8_t WData[5] = {1,2,33,4,5};
uint8_t RData[5];
int main(void){
HAL_Init();
HSE_RCC_Init();
UART1_Init(115200);
SPI_Init();
Key_GPIO_Init();
printf("程序测试\r\n");
while(1){
switch(Key_Scan())
{
case 1:
printf("按键按下了,主机向从机发送数据!\r\n");
/* 发送数据 */
CSS_ENABLE;
HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5);//开启接受发送数据中断。
break;
}
}
}
④stm32f1xx_it.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "stm32f1xx_it.h"
#include "SPI.h"
#include "UART.h"
/**
* @brief:SPI1的中断服务函数
*/
void SPI1_IRQHandler(void)
{
HAL_SPI_IRQHandler(&hspi1);//中断服务总函数
}
/******************* 下面的中断的回调函数 ***************************/
/**
* @brief:SPI1收发数据完成的中断服务函数
*/
extern uint8_t RData[5];
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
CSS_DISABLE; //读取数据完成,关闭NSS
for(uint8_t i = 0; i<sizeof(RData)/sizeof(RData[0]); i++)
{
printf("RData[%d] = %d\r\n",i,RData[i]);
}
}else if(hspi->Instance == SPI2)
{
}
}
从机的代码如下
①SPI.c文件的代码如下
#include "SPI.h"
/**
* @brief:SPI1初始化函数
*/
SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_Init(void)
{
/* SPI1的初始化 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_SLAVE; //模式:从机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_HARD_INPUT; //NSS:硬件控制输入
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
}
/**
* @brief:HAL_SPI_Init()调用此函数
*/
DMA_HandleTypeDef hdma1_Rx; //SP11的DMA1的通道2配置结构体
DMA_HandleTypeDef hdma1_Tx; //SP11的DMA1的通道3配置结构体
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Init; //IO口初始化结构体
if(hspi->Instance == SPI1)
{
/* IO口的初始化:SPI1的CLK = PA5,MISO = PA6,MOSI = PA7,NSS = 硬件控制 */
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入模式
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
/* 1、初始化DMA1的通道2配置 */
__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1的时钟
hdma1_Rx.Instance = DMA1_Channel2; //选择DMA1的通道2
hdma1_Rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设站点数据宽度:8位
hdma1_Rx.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址是否递增:选择不自增
hdma1_Rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //内存站点数据宽度:8位
hdma1_Rx.Init.MemInc = DMA_MINC_ENABLE; //内存地址是否递增:选择自增
hdma1_Rx.Init.Direction = DMA_PERIPH_TO_MEMORY; //传输方向:这里选择外设---->内存
hdma1_Rx.Init.Mode = DMA_CIRCULAR; //计数器传输模式:自动重装
hdma1_Rx.Init.Priority = DMA_PRIORITY_MEDIUM; //通道1传输优先级:选择中等
__HAL_LINKDMA(&hspi1,hdmarx,hdma1_Rx); //SPI1和DMA1构建链接
HAL_DMA_Init(&hdma1_Rx);
HAL_NVIC_SetPriority(DMA1_Channel2_IRQn,3,0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
/* 2、初始化DMA1的通道3配置 */
hdma1_Tx.Instance = DMA1_Channel3; //选择DMA1的通道3
hdma1_Tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设站点数据宽度:8位
hdma1_Tx.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址是否递增:选择不自增
hdma1_Tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //内存站点数据宽度:8位
hdma1_Tx.Init.MemInc = DMA_MINC_ENABLE; //内存地址是否递增:选择自增
hdma1_Tx.Init.Direction = DMA_MEMORY_TO_PERIPH; //传输方向:这里选择内存---->外设
hdma1_Tx.Init.Mode = DMA_CIRCULAR; //计数器传输模式:自动重装
hdma1_Tx.Init.Priority = DMA_PRIORITY_MEDIUM; //通道1传输优先级:选择中等
__HAL_LINKDMA(&hspi1,hdmatx,hdma1_Tx); //SPI1和DMA1构建链接
HAL_DMA_Init(&hdma1_Tx);
HAL_NVIC_SetPriority(DMA1_Channel3_IRQn,3,0);
HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}else if(hspi->Instance == SPI2)
{
}
}
②SPI.h文件的代码如下
#ifndef __SPI_H
#define __SPI_H
#include "stm32f1xx_hal.h"
extern SPI_HandleTypeDef hspi1; //SPI1配置结构体
extern DMA_HandleTypeDef hdma1_Rx; //SP11的DMA1的通道2配置结构体
extern DMA_HandleTypeDef hdma1_Tx; //SP11的DMA1的通道3配置结构体
void SPI_Init(void);
#endif
③main.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "SPI.h"
#include "UART.h"
uint8_t WData[5] = {1,2,33,4,5};
uint8_t RData[5];
int main(void){
HAL_Init();
HSE_RCC_Init();
UART1_Init(115200);
SPI_Init();
printf("程序测试\r\n");
HAL_SPI_TransmitReceive_DMA(&hspi1, WData, RData, 5);//开启接受发送数据DMA。
while(1){
}
}
④stm32f1xx_it.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "stm32f1xx_it.h"
#include "SPI.h"
#include "UART.h"
/**
* @brief:DMA完成的中断服务函数
*/
void DMA1_Channel2_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma1_Rx);//中断服务总函数
}
void DMA1_Channel3_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma1_Tx);//中断服务总函数
}
/******************* 下面的中断的回调函数 ***************************/
/**
* @brief:DMA收发数据完成的中断服务函数
*/
extern uint8_t RData[5];
extern uint8_t WData[5];
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
for(uint8_t i = 0; i<sizeof(RData)/sizeof(RData[0]); i++)
{
printf("主机发送给从机的数据RData[%d] = %d\r\n",i,RData[i]);
}
HAL_SPI_TransmitReceive_DMA(&hspi1, WData, RData, 5);//开启接受发送数据中断。
}else if(hspi->Instance == SPI2)
{
}
}
3、主从通信:主从可交换模式
因为主从通信下,只有主机才能主动向从机发送数据。例如A为主机,B为从机,而通信时只有A才能主动向B发送数据,而B不能主动向A发送数据。若需要B主动向A发送数据时,则需要主从身份交换,将B作为主机,A作为从机。
主从可交换模式:将2个设备都配置为主机,NSS配置为SPI_NSS_HARD_INPUT(硬件控制输入),如图:NSS引脚分别连接着对方的一个IO口。
①A作主机B作从机,则IOA输出低电平即可。
原因:B配置为主机,而NSS配置为SPI_NSS_HARD_INPUT且输入为低电平,即与B配置为主机冲突会产生一个MODF错误,会进入一个错误中断回调,再回调函数里面可配置作为从机的IO口引脚。
②B作主机A作从机,则IOB输出低电平即可。
原因:同理
①SPI.c文件的代码如下
#include "SPI.h"
/**
* @brief:SPI1主机初始化函数
*/
SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_MInit(void)
{
/* SPI1的初始化 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_MASTER; //模式:主模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_HARD_INPUT; //NSS:硬件输入
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
__HAL_SPI_ENABLE_IT(&hspi1, SPI_IT_ERR); //使能错误中断
__HAL_SPI_ENABLE(&hspi1); //使能SPI1
/* 配置SPI1的NVIC */
HAL_NVIC_SetPriority(SPI1_IRQn,3,0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
}
/**
* @brief:SPI1从机初始化函数
*/
void SPI_SInit(void)
{
/* 将其配置为从机 */
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1的时钟
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_SLAVE; //模式:从模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //1个字节
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟空闲低电平
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //第一个边沿采集
hspi1.Init.NSS = SPI_NSS_HARD_INPUT; //NSS:硬件输入
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,72MHz/2
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //高位先行
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //关闭TI模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭CRC校验
HAL_SPI_Init(&hspi1);
}
/**
* @brief:HAL_SPI_Init()调用此函数
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Init; //IO口初始化结构体
if(hspi->Instance == SPI1)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
/* PA3:连接对方NSS */
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP; //通用推挽输出
GPIO_Init.Pin = GPIO_PIN_3; //PA3
GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW; //最大输出速度:最低2MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);//先输出高电平
if(hspi->Init.Mode == SPI_MODE_MASTER) //为主机的引脚配置
{
/* IO口的初始化:SPI1的CLK = PA5,MISO = PA6,MOSI = PA7*/
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
else //为从机的引脚配置
{
GPIO_Init.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Init.Pin = GPIO_PIN_6; //PA6
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM; //最大输出速度:最高10MHz
HAL_GPIO_Init(GPIOA, &GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_INPUT; //输入
GPIO_Init.Pin = GPIO_PIN_5|GPIO_PIN_7; //PA5和PA7
GPIO_Init.Pull = GPIO_NOPULL; //浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
}
}else if(hspi->Instance == SPI2)
{
}
}
②SPI.h文件的代码如下
#ifndef __SPI_H
#define __SPI_H
#include "stm32f1xx_hal.h"
extern SPI_HandleTypeDef hspi1; //SPI1配置结构体
void SPI_MInit(void);
void SPI_SInit(void);
#endif
③main.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "SPI.h"
#include "UART.h"
#include "Key.h"
uint8_t WData[5] = {1,2,33,4,5};
uint8_t RData[5];
int main(void){
HAL_Init();
HSE_RCC_Init();
UART1_Init(115200);
SPI_MInit(); //都先配置为主机
Key_GPIO_Init();
printf("程序测试\r\n");
while(1){
switch(Key_Scan())
{
case 1:
printf("按键按下了!\r\n");
/* 发送数据 */
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET); //PA3先输出低电平,把对方设置为从机
HAL_Delay(20); //等待20ms,等从机配置成功
HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5);//开启接受发送数据中断。
break;
}
}
}
④stm32f1xx_it.c文件的代码如下
#include "stm32f1xx_hal.h"
#include "stm32f1xx_it.h"
#include "SPI.h"
#include "UART.h"
/**
* @brief:SPI1的中断服务函数
*/
void SPI1_IRQHandler(void)
{
HAL_SPI_IRQHandler(&hspi1);//中断服务总函数
}
/******************* 下面的中断的回调函数 ***************************/
/**
* @brief:SPI1收发数据完成的中断服务函数
*/
extern uint8_t RData[5];
extern uint8_t WData[5];
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET); //PA3输出高电平
for(uint8_t i = 0; i<sizeof(RData)/sizeof(RData[0]); i++)
{
printf("RData[%d] = %d\r\n",i,RData[i]);
}
HAL_SPI_DeInit(&hspi1); //对SPI复位
SPI_MInit();//将从机还原为主机
}else if(hspi->Instance == SPI2)
{
}
}
/**
* @brief:错误中断的回调函数
*/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI1)
{
if(hspi->ErrorCode == HAL_SPI_ERROR_MODF)//模式错误
{
printf("我变成从机了!\r\n");
HAL_SPI_DeInit(&hspi1); //对SPI复位
SPI_SInit(); //配置为从机
HAL_SPI_TransmitReceive_IT(&hspi1, WData, RData, 5);//开启接受发送数据中断。
}
}
else if(hspi->Instance == SPI2)
{
}
}