FPGA与STM32的SPI通信 - FPGA主 STM32从
前言
最近项目需要从FPGA向STM32传输数据,选用SPI通信传输,传输数据为32位,后改为8位。
之前写了个stm32从机32位数据接收的,因个人能力不足没成功改成接收8位数据的代码,于是直接让从机接收32位数据,主机传8位数据,取第一组8位数据得了。
具体SPI通信原理就不赘述了,网上很多大神有详细讲解过,此处只贴上自己项目关于SPI通信的代码作学习记录,给有需要的朋友参考。
(本人水平不足,代码写的冗余复杂见谅)
目的:fpga与stm32通过spi通信进行32位数据传输,fpga--主机,stm32--从机(SPI2端口)
方法:fpga与stm32分别编写SPI通信模块,stm32从机借助SPI+DMA来接收数据
工具:fpga开发板ALINX AX7010 与STM32RCT6
SPI选用模式:mode0

FPGA主机SPI通信代码(发送8位,可自行更改为发送32位):
FPGA主机SPI通信代码--顶层模块
FPGA主机SPI通信代码--顶层模块
module va_sine_wave(
        input sys_clk,///系统时钟
        input rst_n,
		input [23:0] F,//上位机传进fpga的数据
        output spi_sck,//spi通讯
        output spi_cs,
        output spi_mosi
    );
	
//clk_1m时钟生成
wire  clk_1m;
clk_1m clk_1m_inst(
	.sys_clk(sys_clk ),
	.rst_n(rst_n),
	.clk_1m(clk_1m)
);
//数据转换
wire [7:0] F_data;
F_Convert F_Convert_inst(
    .F(F),
    .data(F_data)
);
//SPI-test
wire send_done;
wire rx_done,rx_en;
wire tx_done,tx_en;
reg spi_miso;
//assign rx_en = 1;
SPI_MasterToSlave SPI_MasterToSlave_inst(
    .CLK(clk_1m ),//1MHz的时钟
    .RST_N(rst_n),
    .Send_Data(F_data),//要传输给stm32的数据
    .tx_en('d1),
    .SCK(spi_sck),
    .CS(spi_cs),
    .MOSI(spi_mosi),
    .MISO(spi_miso),
    .tx_done(tx_done),
    .send_done(send_done)
);
endmodule
FPGA主机SPI通信代码--1Mhz分频时钟
FPGA主机SPI通信代码--1Mhz分频时钟
module clk_1m(
	input sys_clk,
	input rst_n,
	output reg clk_1m
    );
reg    [25:0]   clk_cnt     ;        //分频计数器
//1Mhz分频时钟
always @ (posedge sys_clk or negedge rst_n) begin
    if (!rst_n) begin
        clk_cnt <= 5'd0;
        clk_1m  <= 1'b0;
    end 
    else if (clk_cnt < 26'd24) 
        clk_cnt <= clk_cnt + 1'b1;       
    else begin
        clk_cnt <= 5'd0;
        clk_1m  <= ~ clk_1m;
    end 
end
endmodule
FPGA主机SPI通信代码--SPI_MasterToSlave
FPGA主机SPI通信代码--SPI_MasterToSlave
module SPI_MasterToSlave(
input CLK,
input RST_N,
input [7:0] Send_Data,//需要spi发送给从机的数据-8位   
input tx_en,//spi发送使能
input rx_en,//spi接收使能
output reg SCK,
output reg CS,
output reg MOSI,//OUTPUT FPGA(fpga主机-fpga发送给从机的数据)
input      MISO,//INPUT FPGA(fpga接收从机传来的数据)
output reg tx_done,//发送完成标志
output reg send_done//每位数据发送完成标志
    );
reg [4:0] tx_state;//这里修改一下位数可以改为发送32位数据
always@(posedge CLK or negedge RST_N)
begin    
    if(RST_N == 0)//复位
    begin
        SCK <= 1'b0;    //SCK初始电平为低
        CS <= 1'b1;     //CS初始电平为高
        MOSI <= 1'b0;   //MOSI初始电平为低
        tx_done <=1'b0;
		send_done <=1'b0;		
		tx_state <= 4'd0;
    end 
    else if(tx_en)//产生SPI时序
    begin
       CS <= 0;//CS拉低准备数据传输		 
       case(tx_state)
       5'd1,5'd3,5'd5,5'd7,5'd9,5'd11,5'd13,5'd15://每次放置数据完毕后 在此拉高时钟线,便于下次的下降沿产生
       begin
          SCK <= 1'b1;//准备在下降沿放置数据,提前将SCK拉高
          tx_state <= tx_state + 4'd1;//切换为数据放置状态(每发完1bit数据进入此一次,将时钟线拉高)
          tx_done <=1'b0;
          send_done <=1'b0;
       end
       5'd0://第7位数据发送状态
        begin
            MOSI <= Send_Data[7];//D7数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd2://第6位数据发送状态
        begin
            MOSI <= Send_Data[6];//D6数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd4://第5位数据发送状态
        begin
            MOSI <= Send_Data[5];//D5数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd6://第4位数据发送状态
        begin
            MOSI <= Send_Data[4];//D4数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd8://第3位数据发送状态
        begin
            MOSI <= Send_Data[3];//D3数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd10://第2位数据发送状态
        begin
            MOSI <= Send_Data[2];//D2数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd12://第1位数据发送状态
        begin
            MOSI <= Send_Data[1];//D1数据
            SCK <= 1'b0;//在下降沿放置数据
            tx_state <= tx_state + 4'd1;//切换状态
            tx_done <=1'b0;
            send_done <=1'b1;
        end
       5'd14://第0位数据发送状态
        begin
            MOSI <= Send_Data[0];
            SCK <= 1'b0;
            tx_state <= tx_state + 4'd1;//4'd15; // 修改为15,继续走一个状态来释放CS,走到16释放CS(目的是实现stm32的SPI通信的硬件控制,稳定传输数据,不然CS一直处于低电平,会一直发送数据给STM32,传输的数据是乱跳的)
            tx_done <= 1'b1;
            send_done <= 1'b1;
        end
        5'd16:begin
            CS <= 1'b1;        // 拉高CS,释放总线
            tx_state <= 4'd0;  // 回到初始状态
            tx_done <= 1'b0;
            send_done <= 1'b0;;
        end
        default:
            begin
            tx_state <= 4'd0;
            tx_done <=1'b0;
            send_done <=1'b0;
            end
        endcase
    end
    else
        begin
        tx_done <=1'b0;
        tx_state <= 4'd0;
        CS <= 1'b1;
        SCK <= 1'b0;
        MOSI <= 1'b0;
        send_done <=1'b0;
        end
end
endmodule
STM32从机SPI通信代码(接收32位数据):
STM32从机SPI通信代码--fpga.c
STM32从机SPI通信代码--fpga.c
#include <stm32f10x.h>
#include "fpga.h"
#include "delay.h"
#include "stm32f10x_spi.h"
#include "stm32f10x_dma.h"
// SPI2_SCK -> PB13
// SPI2_MISO -> PB14
// SPI2_MOSI -> PB15
// SPI2_NSS ->  PB12
static uint8_t spi_buf[4]= {0};
volatile uint8_t spi_data_ready = 0;
volatile uint32_t g_frequency_data = 0;
void SPI2_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef  DMA_InitStructure;	
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	
	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2, ENABLE );
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	    // SCK
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
    GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	    // MISO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	    // MOSI
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
		
		    // NSS
    GPIO_InitStructure.GPIO_Pin = SPI2_CS_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(SPI2_CS_GPIO_PORT, &GPIO_InitStructure);
		
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;//SPI--从机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  //SPI的模式:mode0
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//SPI的模式:mode0
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;//硬件控制
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
	SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI2, &SPI_InitStructure);
		
	SPI_Cmd(SPI2, ENABLE); // enableSPI2
		
	DMA_DeInit(DMA1_Channel4); // SPI2_RX--DMA1_Channel4
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&spi_buf[0];//如果不是传输8位数据,这里应该要改?改为spi_buf
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 4; // 1byte
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//SPI--普通模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);
		
	DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
	//中断优先级设置	
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
		
	NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
		
    //开启DMA
	SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, ENABLE);
	DMA_ClearFlag(DMA1_FLAG_TC4);
	DMA_Cmd(DMA1_Channel4, DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel4, 4);
	DMA_Cmd(DMA1_Channel4, ENABLE);
}
void DMA1_Channel4_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC4) != RESET)
    {
      DMA_ClearITPendingBit(DMA1_IT_TC4);
//        g_frequency_data = ((uint32_t)spi_buf[0] << 24) |
//                           ((uint32_t)spi_buf[1] << 16) |
//                           ((uint32_t)spi_buf[2] << 8)  |
//                           ((uint32_t)spi_buf[3]);	//此处可取32位数据,但需要fpga那边自己也发送32位数据(这里记得修改一下)
		g_frequency_data = spi_buf[0];	//因fpga只传输8位,所以只取第一个数组内数据
        //串口打印
        printf("spi_buf[0] = 0x%02X\r\n", spi_buf[0]);//串口打印
        printf("spi_buf[1] = 0x%02X\r\n", spi_buf[1]);
        printf("spi_buf[2] = 0x%02X\r\n", spi_buf[2]);
        printf("spi_buf[3] = 0x%02X\r\n", spi_buf[3]);
		printf("Received 32-bit data: 0x%08lX\r\n", g_frequency_data);
		spi_data_ready = 1; 
    }
}
STM32从机SPI通信代码--main.c
STM32从机SPI通信代码--main.c
#include <stm32f10x.h>
#include "stm32f10x_rcc.h"
#include "Delay.h"
#include "PeripheralInit.h"
#include "fpga.h"
#include <stdio.h>
#include "stm32f10x_spi.h"
#include "stm32f10x_dma.h"
int main (void) 
{
	unsigned long FREQ;
	SPI2_Init();
	PeripheralInit();		// 外设初始化
	
	printf("STM32 SPI2 Slave Ready to Receive...\r\n");//串口打印
	while (1)
	{
if(spi_data_ready){
		if(spi_data_ready){
		FREQ = g_frequency_data * 10000;//SPI收到的数据在这里使用
		printf("FREQ = %d\r\n", FREQ);
		spi_data_ready = 0;
		Delay_5ms(10);
		DMA_Cmd(DMA1_Channel4, DISABLE);
		DMA_SetCurrDataCounter(DMA1_Channel4, 4); 
      	DMA_Cmd(DMA1_Channel4, ENABLE); 
				}
		}
	}
}
效果图
1.FPGA发送数据


2.STM32串口打印出的数据

注:
1.贴上去的代码仅为项目spi部分的代码,实际效果没测试过,需要的朋友自行更改。
参考资料
【接口时序】4、SPI总线的原理与Verilog实现
FPGA——主机STM32与从机FPGA进行SPI通信验证 
FPGA(主)与STM32(从)SPI通信
FPGA(三)——基于FPGA的SPI通讯协议实现
FPGA学习之路—接口(3)—SPI详解及Verilog源码分析
STM32F103C8T6工控板双SPI互通讯实验
【SPI】STM32 SPI 双机通信,SPI从机模式使用
解决STM32的从机SPI接收数据错位或者出错的问题
                    
                
                
            
        
浙公网安备 33010602011771号