ZYNQ与ESP32S3进行SPI通信
·
SPI通信与有关问题的总结
本文主要说明使用正点原子zynq7010启明星开发板与ESP32进行SPI通信的全过程。在开发过程中,遇到了如下问题:
- 如何配置zynq的端口为SPI端口?
- 如何配置ESP32的端口为SPI端口?
- 传输的数据是乱码怎么办?
- 使用ESP-IDF报错找不到库函数怎么办?
一、ZYNQ的硬件配置
- 配置ZYNQ核心:

这里开启SPI0,与EMIO联通:

这样我们只需要在引脚约束文件中添加约束即可。 - 引出SPI引脚

完成后先生成输出文件,再生成顶层HDL Wapper - 生成Bitstream

打开引脚约束,配置所需引脚,我这里选择开发版边缘的引脚方便寻找和链接

这些引脚在BANK35和BANK34上,做引脚约束和电压设置

最后Ctrl+S保存设置,生成Bitstream

- 最后导出.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));
}
}
}
最终在串口助手上可以看到如下画面:

传输正常,验证成功。
常见问题:
- 如果接收到一堆乱码:

这说明两款芯片没有共地,请将两者的GND连接到一起。 - 如果在vscode中报错找不到头文件:

这是因为没有找到头文件,请找到这个头文件并把目录包含到里面:



加入后再试一次就好了。
更多推荐
所有评论(0)