SPI通讯协议
SPI协议物理层
SPI协议是一种高速全双工的通信总线。SPI设备之间的连接方式如图所示:
SPI通讯使用3条总线及一个片选线,SCK为时钟信号线,MISO为主设备输入/从设备输出,MOSI为主设备输出/从设备输入。
协议层
下图就是SPI通讯的通讯时序: 1)采样时刻,MISO与MOSI的数据才有效,高电平表示为“1”,低电平表示为“0”。 2)通讯的起始信号:片选信号由高变低;SPI的停止信号:片选信号由低变高。
SPI共有4种通讯模式,由CPOL和CPHA决定:
- 时钟极性CPOL ,表示SPI通讯设备处于空闲状态时,SCK的电平信号;CPOL为0时,即指通讯开始前SCK为低电平。
- 时钟相位CPHA ,指数据的采样时刻,CPHA = 0,数据线在SCK时钟线的“奇数边沿”采样;CPHA = 1,数据线在SCK时钟线的“偶数边沿”采样。
SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
---|
0 | 0 | 0 | 低电平 | 奇数边沿 | 1 | 0 | 1 | 低电平 | 偶数边沿 | 2 | 1 | 0 | 低电平 | 奇数边沿 | 3 | 1 | 1 | 低电平 | 偶数边沿 |
STM32的SPI特性及架构
STM32的SPI外设支持最高的时钟频率为fpclk/2(STM32F103 型号的芯片默认 f pclk1 为 72MHz,f pclk2 为 36MHz)。本实验采用双线全双工模式。
STM32的SPI架构
- 通讯引脚 ,STM32有多个SPI外设,使用对应SPI外设引脚,其中片选引脚一般采用普通IO口,使用软件控制片选段。
- 时钟控制逻辑 ,SCK 线的时钟信号,由波特率发生器根据“控制寄存器 CR1”中的 BR[0:2]位控制。通过配置“控制寄存器 CR”的“CPOL 位”及“CPHA”位可以把 SPI 设置成前面分析的 4 种 SPI模式。
- 数据控制逻辑通过写 SPI的“数据寄存器 DR”把数据填充到发送 F 缓冲区中,通讯读“数据寄存器 DR”,可以获得接收缓冲区内容。
- 整体控制逻辑整体控制逻辑负责协调整个 SPI 外设,控制逻辑的工作模式根据我们配置的**“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的 SPI 模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”**,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。
主模式通讯流程
SPI初始化结构体(STM32标准库)
配置完这些结构体成员后,我们要调用 SPI_Init 函数把这些参数写入到寄存器中,实现 SPI的初始化,然后调用 SPI_Cmd 来使能 SPI外设。
STM32实验代码
本实验采用SPI模式3进行主模式代码编写,编程要点如下:
- 初始化通讯使用的目标引脚及端口时钟;
- 使能SPI外设时钟
- 配置 SPI外设的模式、地址、速率等参数并使能 SPI外设;
- 编写SPI按照字节收发的函数
新建一个c文件,用于存放SPI初始化及读写数据相关函数。
#include "./fpga/bsp_spi_fpga.h"
void SPI_FPGA_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
FPGA_SPI_APBxClock_FUN ( FPGA_SPI_CLK, ENABLE );
FPGA_SPI_CS_APBxClock_FUN ( FPGA_SPI_CS_CLK|FPGA_SPI_SCK_CLK|
FPGA_SPI_MISO_PIN|FPGA_SPI_MOSI_PIN, ENABLE );
GPIO_InitStructure.GPIO_Pin = FPGA_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(FPGA_SPI_CS_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FPGA_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(FPGA_SPI_SCK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FPGA_SPI_MISO_PIN;
GPIO_Init(FPGA_SPI_MISO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FPGA_SPI_MOSI_PIN;
GPIO_Init(FPGA_SPI_MOSI_PORT, &GPIO_InitStructure);
SPI_FPGA_CS_HIGH();
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(FPGA_SPIx , &SPI_InitStructure);
SPI_Cmd(FPGA_SPIx , ENABLE);
}
u8 SPI_FPGA_SendByte(u8 byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
while (SPI_I2S_GetFlagStatus(FPGA_SPIx , SPI_I2S_FLAG_TXE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
SPI_I2S_SendData(FPGA_SPIx , byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
while (SPI_I2S_GetFlagStatus(FPGA_SPIx , SPI_I2S_FLAG_RXNE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
return SPI_I2S_ReceiveData(FPGA_SPIx );
}
u8 SPI_FPGA_ReadByte(void)
{
return (SPI_FPGA_SendByte(Dummy_Byte));
}
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
FPGA_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
bsp_spi_fpga.h内容如下:
#ifndef __SPI_FPGA_H
#define __SPI_FPGA_H
#include "stm32f10x.h"
#include <stdio.h>
#define FPGA_SPIx SPI2
#define FPGA_SPI_APBxClock_FUN RCC_APB1PeriphClockCmd
#define FPGA_SPI_CLK RCC_APB1Periph_SPI2
#define FPGA_SPI_CS_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FPGA_SPI_CS_CLK RCC_APB2Periph_GPIOC
#define FPGA_SPI_CS_PORT GPIOC
#define FPGA_SPI_CS_PIN GPIO_Pin_3
#define FPGA_SPI_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FPGA_SPI_SCK_CLK RCC_APB2Periph_GPIOB
#define FPGA_SPI_SCK_PORT GPIOB
#define FPGA_SPI_SCK_PIN GPIO_Pin_13
#define FPGA_SPI_MISO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FPGA_SPI_MISO_CLK RCC_APB2Periph_GPIOB
#define FPGA_SPI_MISO_PORT GPIOB
#define FPGA_SPI_MISO_PIN GPIO_Pin_14
#define FPGA_SPI_MOSI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FPGA_SPI_MOSI_CLK RCC_APB2Periph_GPIOB
#define FPGA_SPI_MOSI_PORT GPIOB
#define FPGA_SPI_MOSI_PIN GPIO_Pin_15
#define SPI_FPGA_CS_LOW() GPIO_ResetBits( FPGA_SPI_CS_PORT, FPGA_SPI_CS_PIN )
#define SPI_FPGA_CS_HIGH() GPIO_SetBits( FPGA_SPI_CS_PORT, FPGA_SPI_CS_PIN )
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
#define FPGA_DEBUG_ON 1
#define FPGA_INFO(fmt,arg...) printf("<<-FPGA-INFO->> "fmt"\n",##arg)
#define FPGA_ERROR(fmt,arg...) printf("<<-FPGA-ERROR->> "fmt"\n",##arg)
#define FPGA_DEBUG(fmt,arg...) do{\ if(FPGA_DEBUG_ON)\ printf("<<-FPGA-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\ }while(0)
void SPI_FPGA_Init(void);
u8 SPI_FPGA_ReadByte(void);
u8 SPI_FPGA_SendByte(u8 byte);
void Delay(__IO uint32_t nCount);
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
#endif
工程主函数为:
int main(void)
{
LED_GPIO_Config();
LED_BLUE;
USART_Config();
printf("\r\n 这是一个STM32与FPGA的通讯实验!\r\n");
SPI_FPGA_Init();
Temp = 123;
SPI_FPGA_CS_LOW();
SPI_FPGA_SendByte(Temp);
SPI_FPGA_CS_HIGH();
Delay(10000);
printf("\r\n 写入的数据为:%d \r\t", Temp);
SPI_FPGA_CS_LOW();
SPI_FPGA_SendByte(245);
SPI_FPGA_CS_HIGH();
Delay(10000);
SPI_FPGA_CS_LOW();
Temp1 = SPI_FPGA_SendByte(Dummy_Byte);
SPI_FPGA_CS_HIGH();
printf("\r\n 读出的数据为:%d \r\n", Temp1);
}
FPGA从机代码编写
module spi
(
input clk ,
input rst_n ,
input CS_N ,
input SCK ,
input MOSI ,
output reg MISO ,
output led ,
output led1
);
wire [7:0] txd_data ;
assign txd_data = 8'b001_1000;
reg [7:0] rxd_data;
wire rxd_flag;
reg [7:0] spi_cnt;
reg sck_r0,sck_r1;
wire sck_n,sck_p;
always@(posedge clk or negedge rst_n)
if(!rst_n)
begin
sck_r0 <= 1'b0;
sck_r1 <= 1'b0;
end
else
begin
sck_r0 <= SCK;
sck_r1 <= sck_r0;
end
assign sck_n = (~sck_r0 & sck_r1)? 1'b1:1'b0;
assign sck_p = (~sck_r1 & sck_r0)? 1'b1:1'b0;
reg rxd_flag_r;
reg [2:0] rxd_state;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rxd_data <= 1'b0;
rxd_flag_r <= 1'b0;
rxd_state <= 1'b0;
end
else if(sck_p && !CS_N)
begin
case(rxd_state)
3'd0:begin
rxd_data[7] <= MOSI;
rxd_flag_r <= 1'b0;
rxd_state <= 3'd1;
end
3'd1:begin
rxd_data[6] <= MOSI;
rxd_state <= 3'd2;
end
3'd2:begin
rxd_data[5] <= MOSI;
rxd_state <= 3'd3;
end
3'd3:begin
rxd_data[4] <= MOSI;
rxd_state <= 3'd4;
end
3'd4:begin
rxd_data[3] <= MOSI;
rxd_state <= 3'd5;
end
3'd5:begin
rxd_data[2] <= MOSI;
rxd_state <= 3'd6;
end
3'd6:begin
rxd_data[1] <= MOSI;
rxd_state <= 3'd7;
end
3'd7:begin
rxd_data[0] <= MOSI;
rxd_flag_r <= 1'b1;
rxd_state <= 3'd0;
end
default: ;
endcase
end
end
reg rxd_flag_r0,rxd_flag_r1;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rxd_flag_r0 <= 1'b0;
rxd_flag_r1 <= 1'b0;
end
else
begin
rxd_flag_r0 <= rxd_flag_r;
rxd_flag_r1 <= rxd_flag_r0;
end
end
assign rxd_flag = (~rxd_flag_r1 & rxd_flag_r0)? 1'b1:1'b0;
reg [2:0] txd_state;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
txd_state <= 1'b0;
end
else if(sck_n && !CS_N)
begin
case(txd_state)
3'd0:begin
MISO <= txd_data[7];
txd_state <= 3'd1;
end
3'd1:begin
MISO <= txd_data[6];
txd_state <= 3'd2;
end
3'd2:begin
MISO <= txd_data[5];
txd_state <= 3'd3;
end
3'd3:begin
MISO <= txd_data[4];
txd_state <= 3'd4;
end
3'd4:begin
MISO <= txd_data[3];
txd_state <= 3'd5;
end
3'd5:begin
MISO <= txd_data[2];
txd_state <= 3'd6;
end
3'd6:begin
MISO <= txd_data[1];
txd_state <= 3'd7;
end
3'd7:begin
MISO <= txd_data[0];
txd_state <= 3'd0;
end
default: ;
endcase
end
end
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
spi_cnt <= 8'd0;
else if(rxd_flag == 1'b1)
spi_cnt <= spi_cnt + 1'b1;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
led <= 1'b0;
else if(rxd_data == 8'd123)
led <= 1'b1;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
led1 <= 1'b0;
else if(rxd_data == 8'd245 && spi_cnt == 8'd2)
led1 <= 1'b1;
endmodule
实验结果
STM32依次给FPGA发送数据123、245并接收FPGA发送过来的数据,通过串口打印出;FPGA给STM32发送数据8’b0001_1000。 FPGA接收的数据为123,则点亮led0灯;如果FPGA第二次接收到的数据为245,则点亮led1灯。 实验结果如图所示: STM32的串口打印信息: 本实验使用的FPGA开发板是基于 Xilinx 公司的 Spartan6 系列 FPGA,型号为 XC6SLX9。
|