多个单片机之间的SPI主从通讯

工程文件链接:

链接:https://pan.baidu.com/s/1RXp9lw2ZqyglQSwKnw7Siw?pwd=6666 
提取码:6666

工程里面有很多例子都是看B站视频手打的(这次代码只用到了YJSPI文件夹下面的.C和.H文件其余可以忽略),只是验证通讯和配置

一,概述

1.因为工作是从事PCBA测试软件开发的(上位机),之前做过的很多项目都是一个单片机完成所有功能,做了有段时间了无非就是那些东西的操控,继电器,电压,电流,PWM,串口,外部IO信号采集,无非是通道的多少,可能这次项目需要继电器多一点,其他项目需要测PWM又多一点。

搞去搞来也就那些东西,而且每次重新画板子都是因为通道和采集点或者继电器不够,这次就打算直接把这些常用功能模块化,一个单片机控制继电器就只控制继电器,测电压的单片机就只测电压,但是有个问题就是他们之间怎么样联系起来,起初想啊是IIC,因为方便线少。后边想了下用IIC挂载多了可能会有影响,我也指不定要挂载多少个设备.所以打算用SPI线多点就是不影响.

后边我会把这次做好的项目模块上传现在这个只是实验,验证可行性.

(如果有大佬有更好的方法欢迎指正 有奖励)

二.实验开始

接线参照下图我用了三片STM32C6T6

主机通过CSS线来选择要和哪一个单片机通讯,从机SCK,MOSI,MISO全部并到主机对应点去,就收数据时候,从机需要判断CSS线是否为低电平来确定是否接收主机发送的数据。调试的时候通过串口把数据打印出来

原理就这样.

三,步骤

1.主机SPI(初始化GPIO,配置硬件SPI,从机CSS用到的引脚默认拉高)

2.从机SPI(初始化GPIO,配置硬件SPI,从机轮询判断端口是否为低电平)

3.示波器查看主机发,从机收

4.示波器查看从机发主机收(因为从机无法主动发所以主机需要先发再收)

五,主机SPI代码

主机硬件SPI(GPIO)初始化:

//CSS:PA0,PA1
//SCK:PA5
//MOSI:PA7
//MOSI:PA6
void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//主机控制CSS线,所以设置输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_InitStructure.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &SPI_InitStructure);
	
	SPI_Cmd(SPI1, ENABLE);
	GPIO_WriteBit(GPIOA, GPIO_Pin_0, 1);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, 1);
	MySPI_W_SS(1);
}

主机SPI(读写):

//CSS线控制 
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)BitValue);
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}
//读
uint8_t MySPI_SlaveReceive(void)
{
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    return SPI_I2S_ReceiveData(SPI1);
}
//写
void MySPI_SlaveSend(uint8_t data)
{
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, data);
}

主机SPI  Main运行代码

由于实验我就只发送两个字节过去,然后再读取从机返回给我的数据

  MySPI_Init();
	while (1)
	{

 MySPI_Start();

 MySPI_SlaveSend(0XA8);

 MySPI_SlaveSend(0XA9);

 uint8_t re = MySPI_SlaveReceive();

 MySPI_Stop();

printf("%d\r\n",re);
	}

六,从机SPI代码

从机机硬件SPI(GPIO)初始化:

先来一个错位示范:

void MySPI_Init(void)
{
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    // CSS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//这里需要读取CSS电平,所以配置浮空
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //SCK, MISO, MOSI 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;//从机
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);
}

这个才是正确的从机SPI的GPIO配置

	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    // CSS 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //MOSI 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	  //MISO 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
		//SCK  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
		SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);

从机SPI(读写):

//从机只需要读写就可以了其余不需要
uint8_t MySPI_SlaveReceive(void)
{
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    return SPI_I2S_ReceiveData(SPI1);
}

void MySPI_SlaveSend(uint8_t data)
{
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, data);
}

从机SPI  Main运行代码

简单点读取主机发送的再,返回给主机0X88,0X8A

MySPI_Init();

	while (1)
	{

        if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == Bit_RESET)
        {   
          uint8_t receivedByte = MySPI_SlaveReceive();

           uint8_t receivedByte2 = MySPI_SlaveReceive();

            printf("%d%d\r\n",receivedByte,receivedByte2); 
 
              MySPI_SlaveSend(0X88);   
      MySPI_SlaveSend(0X8A);  

     }
	}

七,实际效果:

本来想把几个波形都显示出来的  可惜我逻辑分析仪在Win11用不了

主机发从机收

从机发送主机收

MISO和MOSI波形

分割线>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

在最近调试中发现 SPI主从通讯的时候字节位老是错位,例如我主机发送1,2,3,4从机收到后把1,2,3,4返回到主机,主机通过串口打印出收到的数据。  

实际测试中发现 串口打印的数据是

第一次:0,1,2,3

第二次:4,1,2,3

第三次:4,1,2,3

往后N次都是这样的.....(可我发送的明明就是1,2,3,4)

就因为第一个数据位错位后导致后边数据全部错位而且无法纠正(不管你怎么样调都是错位的)

我网上查找了很多资料,说什么前面加个读取,还说什么主机和从机的GPIO配置一样,差别就是在于收发数据,等等都在胡说八道,感觉挺容易误导别人。

1.这里澄清一点 SPI主机和从机的GPIO配置肯定不同

2.我看了很多别人发的不知道从哪里来的,基本主机SPI配置和从机SPI的GPIO配置一模一样,我也不清楚这样也能通讯上妈蛋奇怪.

3.来下面直接看手册,主从配置方法不仅不一样而且区别大了去了。(我自己上边代码都是错误的,我就不修正了)

4.如果GPIO主从配置正确,那么下载好程序到各自单片机上面,先复位从机在复位主机(这点很重要)

这是我修改后的发送打印

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 单片机通过SPI总线可以接多个传感器模块。SPI(Serial Peripheral Interface)是一种串行外设接口协议,可以实现单片机与外部设备之间的高速数据传输和通信。通过SPI总线接多个传感器模块可以通过一个主机控制多个从机的方式,简化了硬件设计和数据处理的复杂性。 在接多个传感器模块时,需要使用片选信号(CS)来选择需要通信的从机设备。主机通过控制片选信号的高低电平来选择与之通信的特定从机设备。当主机选择一个从机设备后,就可以通过SPI总线进行数据传输。通过使用多路复用器等器件,可以将多个从机的片选信号与单片机的引脚相连接,从而实现多个传感器模块的并行通信。 在通信过程中,单片机作为主机可以通过发送命令字和读取从机的响应数据,实现对传感器模块的配置、控制和数据读取。同时,单片机也可以使用中断机制来接收从机发送的中断信号,实现实时数据的获取。 总的来说,通过SPI总线接多个传感器模块可以实现简洁的硬件设计、高速的数据传输和灵活的控制,从而满足多传感器系统的需求。但在设计中需要注意合理规划片选信号的使用和传输时序的控制,以确保数据的准确和稳定传输。 ### 回答2: 单片机通过SPI总线接多个传感器模块是一种常见的系统设计方案。SPI(Serial Peripheral Interface)是一种同步串行通信协议,可以实现单片机与外部设备之间的数据交换。通过SPI总线,单片机可以同时接入多个传感器模块,实现数据的传输和控制。 首先,单片机需要具备SPI接口功能。SPI接口通常包含四条信号线:主片选(SS)、时钟(SCLK)、主输入/从输出(MOSI)、主输出/从输入(MISO)。单片机通过这四条信号线与各个传感器模块进行通信。 其次,每个传感器模块都需要有一个唯一的片选(SS)信号线,用于选择与单片机进行通信的传感器模块。当单片机需要与某个传感器模块通信时,将其片选信号线拉低,选择该传感器模块;其他传感器模块的片选信号线保持高电平,处于非选择状态。 然后,单片机通过时钟和数据线与所选传感器模块进行数据交换。在通信开始时,单片机通过时钟线时钟脉冲的边沿触发传感器模块在数据线上发送数据;单片机则在数据线上发送相应的控制指令。数据的传输以字节为单位,可以实现双向通信,单片机既可以发送数据给传感器模块,也可以接收传感器模块返回的数据。 最后,单片机切换至下一个传感器模块时,先将当前传感器模块的片选信号线拉高,再将下一个传感器模块的片选信号线拉低,然后进行数据交换。这样,单片机可以轮流与多个传感器模块进行通信,实现对多个传感器模块的控制和数据采集。 通过SPI总线接多个传感器模块,单片机可以灵活地扩展传感器数量,实现更加复杂的功能和应用。然而,在设计过程中需要注意总线的负载能力、传输速率以及各个传感器模块的通信协议和数据格式等因素,以确保系统的稳定性和可靠性。 ### 回答3: 单片机通过SPI总线可以接多个传感器模块。SPI(串行外设接口)是一种通信协议,它允许多个设备通过主从方式进行通信。在SPI总线上,单片机可以充当主设备,而传感器模块则充当从设备。 首先,单片机需要一个SPI主控制器来通过SPI总线进行通信。主控制器可以与多个从设备进行通信,并且能够选择与哪个从设备进行通信。每个从设备都会有一个唯一的片选引脚,用于选择与之通信的从设备。 接下来,每个传感器模块需要与单片机相连。它们之间需要四根信号线:SCK(时钟信号)、MISO(主输入从输出)、MOSI(主输出从输入)和SS(片选信号)。SCK信号用于同步数据传输的时钟控制,MISO信号是从设备输出到主设备的数据线,MOSI信号是主设备输出到从设备的数据线,而SS信号则用于选择与之通信的从设备。 当单片机需要与某个传感器模块进行通信时,它首先通过片选引脚选择该从设备。然后,通过主控制器发送和接收数据。主控制器向传感器模块发送命令或请求数据,并从传感器模块接收响应或传感器数据。数据传输是通过时钟信号和数据线同步进行的。 通过SPI总线,单片机可以与多个传感器模块同时通信,提高了系统的扩展能力和灵活性。它可以有效地管理多个传感器模块,从而实现更复杂的功能和更准确的数据采集。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值