SPI通信与有关问题的总结

本文主要说明使用正点原子zynq7010启明星开发板与ESP32进行SPI通信的全过程。在开发过程中,遇到了如下问题:

  1. 如何配置zynq的端口为SPI端口?
  2. 如何配置ESP32的端口为SPI端口?
  3. 传输的数据是乱码怎么办?
  4. 使用ESP-IDF报错找不到库函数怎么办?

一、ZYNQ的硬件配置

  1. 配置ZYNQ核心:
    核心配置
    这里开启SPI0,与EMIO联通:
    SPI设置
    这样我们只需要在引脚约束文件中添加约束即可。
  2. 引出SPI引脚
    在这里插入图片描述
    完成后先生成输出文件,再生成顶层HDL Wapper
  3. 生成Bitstream
    在这里插入图片描述
    打开引脚约束,配置所需引脚,我这里选择开发版边缘的引脚方便寻找和链接
    引脚

这些引脚在BANK35和BANK34上,做引脚约束和电压设置
在这里插入图片描述
最后Ctrl+S保存设置,生成Bitstream
在这里插入图片描述

  1. 最后导出.xsa文件,包含Bitstream文件,打开vitis软件,编写代码。

二、ZYNQ的软件配置

在vitis中编写如下代码:

#include "xparameters.h"
#include "xspips.h"
#include "xil_printf.h"
#include "sleep.h"

#define SPI_DEVICE_ID      XPAR_XSPIPS_0_DEVICE_ID

XSpiPs SpiInstance;

u8 SendBuffer[32] __attribute__((aligned(32)));
u8 RecvBuffer[32] __attribute__((aligned(32)));

int main(void)
{
    int Status;
    XSpiPs_Config *SpiConfig;
    int k = 0;

    xil_printf("\r\n=== Zynq SPI Master Test ===\r\n");

    // 1. 查找配置
    SpiConfig = XSpiPs_LookupConfig(SPI_DEVICE_ID);
    if (NULL == SpiConfig) {
        xil_printf("ERROR: SPI config not found!\r\n");
        return XST_FAILURE;
    }
    xil_printf("SPI BaseAddress: 0x%08X\r\n", SpiConfig->BaseAddress);

    // 2. 初始化
    Status = XSpiPs_CfgInitialize(&SpiInstance, SpiConfig, SpiConfig->BaseAddress);
    if (Status != XST_SUCCESS) {
        xil_printf("ERROR: SPI init failed!\r\n");
        return XST_FAILURE;
    }

    // 3. 自检
    Status = XSpiPs_SelfTest(&SpiInstance);
    if (Status != XST_SUCCESS) {
        xil_printf("WARNING: SPI self-test failed!\r\n");
    } else {
        xil_printf("SPI self-test passed.\r\n");
    }

    // 4. 设置选项
    // Mode 0: CPOL=0, CPHA=0 (默认)
    XSPIPS_CLK_ACTIVE_LOW_OPTION
    Status = XSpiPs_SetOptions(&SpiInstance,
                               XSPIPS_MASTER_OPTION |
                               XSPIPS_FORCE_SSELECT_OPTION);
    if (Status != XST_SUCCESS) {
        xil_printf("ERROR: Set options failed!\r\n");
        return XST_FAILURE;
    }

    // 5. 设置时钟分频
    // 建议调试时用较慢的时钟,比如 PRESCALE_64 或 PRESCALE_128
    XSpiPs_SetClkPrescaler(&SpiInstance, XSPIPS_CLK_PRESCALE_64);
    xil_printf("SPI clock prescaler set to 64\r\n");

    // 6. 选择 CS0
    XSpiPs_SetSlaveSelect(&SpiInstance, 0x00);

    // 7. 填充测试数据
    for (int i = 0; i < 32; i++) {
        SendBuffer[i] = i;  // 0x00, 0x01, 0x02 ... 0x1F
        RecvBuffer[i] = 0;
    }

    xil_printf("Starting transmission loop...\r\n");
    xil_printf("Send data: ");
    for (int i = 0; i < 32; i++) {
        xil_printf("%02X ", SendBuffer[i]);
    }
    xil_printf("\r\n\r\n");

    // 8. 主循环
    while (1) {
        // 清空接收缓冲区
        memset(RecvBuffer, 0, 32);

        // 传输
        Status = XSpiPs_PolledTransfer(&SpiInstance, SendBuffer, RecvBuffer, 32);

        if (Status == XST_SUCCESS) {
            xil_printf("[%d] TX OK. RX: ", k);
            for (int i = 0; i < 8; i++) {  // 只打印前8字节
                xil_printf("%02X ", RecvBuffer[i]);
            }
            xil_printf("...\r\n");
        } else {
            xil_printf("[%d] TX FAILED! Status=%d\r\n", k, Status);
        }

        k++;
        usleep(500000);  // 500ms 间隔,方便观察
    }

    return 0;
}

三、ESP-IDF代码编写

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_slave.h"
#include "driver/gpio.h"
#include "esp_log.h"

static const char *TAG = "SPI_SLAVE";


#define GPIO_MOSI   3
#define GPIO_MISO   46
#define GPIO_SCLK   40
#define GPIO_CS     45

#define SPI_BUF_SIZE  32


WORD_ALIGNED_ATTR uint8_t recvbuf[SPI_BUF_SIZE];
WORD_ALIGNED_ATTR uint8_t sendbuf[SPI_BUF_SIZE];

void app_main(void)
{
    esp_err_t ret;

    ESP_LOGI(TAG, "Initializing SPI Slave...");

    // SPI 总线配置
    spi_bus_config_t buscfg = {
        .mosi_io_num = GPIO_MOSI,
        .miso_io_num = GPIO_MISO,
        .sclk_io_num = GPIO_SCLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = SPI_BUF_SIZE,
    };

    // SPI Slave 接口配置
    spi_slave_interface_config_t slvcfg = {
        .mode = 0,                    // SPI Mode 0 (CPOL=0, CPHA=0)
        .spics_io_num = GPIO_CS,      // 片选引脚
        .queue_size = 3,              // 事务队列大小
        .flags = 0,
        .post_setup_cb = NULL,
        .post_trans_cb = NULL,
    };

    // CS 引脚上拉,防止误触发
    gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);

    // 初始化 SPI Slave
    ret = spi_slave_initialize(SPI2_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "SPI Slave init failed: %s", esp_err_to_name(ret));
        return;
    }

    ESP_LOGI(TAG, "SPI Slave initialized. Waiting for data...");

    // 准备用于回传的数据(可选)
    memset(sendbuf, 0xAA, SPI_BUF_SIZE);  // 回传 0xAA 给主机

    while (1) {
        spi_slave_transaction_t trans;
        memset(&trans, 0, sizeof(trans));
        memset(recvbuf, 0, SPI_BUF_SIZE);

        trans.length = SPI_BUF_SIZE * 8;  // 长度单位是 bit
        trans.rx_buffer = recvbuf;
        trans.tx_buffer = sendbuf;        // 同时可以发送数据给主机

        // 阻塞等待主机发起传输
        ret = spi_slave_transmit(SPI2_HOST, &trans, portMAX_DELAY);

        if (ret == ESP_OK) {
            // trans.trans_len 是实际传输的 bit 数
            int recv_bytes = trans.trans_len / 8;
            
            printf("Received %d bytes: ", recv_bytes);
            for (int i = 0; i < recv_bytes; i++) {
                printf("%02X ", recvbuf[i]);
            }
            printf("\n");
        } else {
            ESP_LOGE(TAG, "SPI receive error: %s", esp_err_to_name(ret));
        }
    }
}
 

最终在串口助手上可以看到如下画面:
在这里插入图片描述
传输正常,验证成功。

常见问题:

  1. 如果接收到一堆乱码:
    在这里插入图片描述
    这说明两款芯片没有共地,请将两者的GND连接到一起。
  2. 如果在vscode中报错找不到头文件:
    在这里插入图片描述
    这是因为没有找到头文件,请找到这个头文件并把目录包含到里面:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    加入后再试一次就好了。

更多推荐