当前位置:   article > 正文

树莓派(主)与STM32(从)使用SPI通信_树莓派 spi

树莓派 spi

1.实验目的

使用树莓派向 STM32 发送数据,STM32 收到数据后通过串口的方式将数据打印到电脑上,同时返回给树莓派数据。树莓派接收到数据后打印在控制台上。
SPI 的配置为

树莓派主机
STM32 从机
全双工
8 bit 传输
工作模式 0 :CPOL|CPHA = 00
MSB 优先
禁止 CRC 校验

2.SPI 简介

SPI(Serial Peripheral Interface,串行外设接口)是 Motorola 公司提出的一种同步串行数据传输标准

2.1 接口

SPI 接口经常被称为 4 线串行总线,以主/从方式工作,数据传输过程由主机初始化,其使用的 4 条信号线分别为:

  1. SCLK:串行时钟,用来同步数据传输,由主机输出,从机不用配置时钟
  2. MOSI:主机输出从机输入数据线,通常先传输 MSB ;
  3. MISO:主机输入从机输出数据线,通常先传输 LSB ;
  4. CS:片选线,低电平有效,由主机输出。
    在 SPI 总线上,某一时刻可以出现多个从机,但只能存在一个主机,主机通过片选线来确定要通信的从机。连接是对应相连,如下所示
    SCLK-----SCLK
    MOSI-----MOSI
    MISO-----MISO
    CS-----CS

2.2 数据传输

在一个 SPI 时钟周期内,会完成如下操作:

  • 主机通过 MOSI 线发送 1 位数据,从机通过该线读取这 1 位数据;
  • 从机通过 MISO 线发送 1 位数据,主机通过该线读取这 1 位数据。
    这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
    在这里插入图片描述

2.3 时钟极性和时钟相位

在 SPI 操作中,最重要的两项设置就是时钟极性( CPOL 或 UCCKPL )和时钟相位( CPHA 或 UCCKPH )。

  • CPOL 表示时钟信号的初始电平的状态(就是空闲状态),CPOL 为 0 表示时钟信号初始状态为低电平,为 1 表示时钟信号的初始电平是高电平。
  • CPHA 表示在哪个时钟沿采样数据,CPHA 为 0 表示在首个时钟变化沿采样数据,而 CPHA 为 1 则表示在第二个时钟变化沿采样数据。
    主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。所以为了保证主从机正确通信,应使得它们的SPI具有相同的时钟极性和时钟相位。

2.4 SPI的4种工作模式

由于 CPOL 和 CPHA 都有两种不同状态,所以 SPI 分成了 4 种模式。我们在开发的时候,使用比较多的是模式 0 和模式 3 ,如下表所示

SPI工作模式CPOLCPHASCL空闲状态采样边沿采样时刻
000低电平上升沿奇数边沿
101低电平下降沿偶数边沿
210高电平下降沿奇数边沿
311高电平上升沿偶数边沿

2.5 优缺点

优点:

  • 支持全双工操作
  • 操作简单
  • 数据传输速率较高

缺点:

  • 需要占用主机较多的口线(每个从机都需要一根片选线)
  • 只支持单个主机

3.树莓派部分

3.1 开启SPI

树莓派默认是没有开启 SPI 的功能的,我们需要手动去开启一下
在终端输入

raspi-config

然后跟着下面的图进行操作(小键盘上下左右是选择,回车是确定)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
输入命令

ls /dev/spi*

出现如下图的样子,就是成功打开了
在这里插入图片描述

3.2 安装vscode(可选)

标题是超链接,点击跳转

3.3 下载bcm支持包

4.STM32部分(只讲解SPI和主函数部分)

4.1 spi.c 中 spi 的初始化代码

/* SPI1 引脚初始化,会在HAL_SPI_Init的时候调用*/
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if(spiHandle->Instance==SPI1)
    {
        __HAL_RCC_SPI1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**SPI1 引脚配置
        PA4     ------> SPI1_NSS
        PA5     ------> SPI1_SCK
        PA6     ------> SPI1_MISO
        PA7     ------> SPI1_MOSI
        */
        GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

/* SPI1 配置初始化 */
void MX_SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_SLAVE;// 从机
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;// 全双工
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//8 bit 传输
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;//CPOL = 0
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//CPHA = 0
    hspi1.Init.NSS = SPI_NSS_HARD_INPUT;//片选硬件输入
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB 优先
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//禁止 CRC 校验
    hspi1.Init.CRCPolynomial = 10;
    if (HAL_SPI_Init(&hspi1) != HAL_OK)//判断是否初始化成功
    {
        Error_Handler();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

4.2 main.c

#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"

void SystemClock_Config(void);//函数声明,实现在下面

/* 输出和输入重定向,主要用于 printf 串口输出 */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
    return ch;
}
int fgetc(FILE *f)
{
    uint8_t ch = 0;
    HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    return ch;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();//串口初始化
    MX_SPI1_Init();//SPI1初始化
    HAL_Delay(1000);

    uint8_t data2[10]={0};//接收数据
    uint8_t send_data2[10]={0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6};//发送数据
    HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)send_data2, (uint8_t *)data2,10,0xff);//提前将数据放进发送缓存区
    while (1)
    {		
        HAL_GPIO_WritePin(Start_GPIO_Port,Start_Pin,GPIO_PIN_RESET);
        HAL_Delay(100);
        HAL_GPIO_WritePin(Start_GPIO_Port,Start_Pin,GPIO_PIN_SET);
        
        /* 接收10个字节的同时,发送10个字节出去,等待时间为 65535ms ,超时则不继续等待 */
        HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)send_data2, (uint8_t *)data2,10,65535);
        /* 通过串口发送给电脑 */
        for(int i=0;i<10;i++)
        {
            printf("0x%02X\n",data2[i]);//指定为以16进制的形式格式化输出
        }				
        HAL_Delay(2000);//延时2s
    }
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 4;
    RCC_OscInitStruct.PLL.PLLN = 168;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
    __disable_irq();
    while (1)
    {
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

源码见本文末尾

5.验证结果

下面是树莓派的引脚图
在这里插入图片描述

按照下面的方式连接树莓派与 STM32 的引脚
树莓派--------STM32
MOSI/#10--------PA7
MISO/#9----------PA6
SCLK/#11--------PA5
GND---------------GND

cd “/home/pi/SPI_test/bcm2835-1.71/examples/spi/” && gcc *.c -o spi && "/home/pi/SPI_test/bcm2835-1.71/examples/spi/"spi

在这里插入图片描述
在这里插入图片描述

6.源码

树莓派bcm2835-1.71(带本实验所需程序)
STM32F407从机SPI使用HAL库轮询方式

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/689143
推荐阅读
相关标签
  

闽ICP备14008679号