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

工程文件链接:

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

多个单片机之间的SPI主从通讯_第1张图片

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

一,概述

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

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

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

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

二.实验开始

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

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

原理就这样.

多个单片机之间的SPI主从通讯_第2张图片三,步骤

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用不了

主机发从机收

多个单片机之间的SPI主从通讯_第3张图片

从机发送主机收

MISO和MOSI波形

多个单片机之间的SPI主从通讯_第4张图片

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

在最近调试中发现 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)

多个单片机之间的SPI主从通讯_第5张图片

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

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

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

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

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

多个单片机之间的SPI主从通讯_第6张图片

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

这是我修改后的发送打印

多个单片机之间的SPI主从通讯_第7张图片

你可能感兴趣的:(单片机,嵌入式硬件)