typora-copy-images-to: typora_picture
基于FPGA与MCU通信的SPI协议设计
1. SPI总线协议介绍及硬件设计
1.1 SPI总线协议介绍及硬件设计
SPI总线是一种同步串行外设接口。(Serial Peripheral Interface,串行外设接口)
PIN | 定义 |
---|---|
NSS | 从机选择线(低电平有效) |
SCK | 串行时钟线 |
MOSI | 主机输出 / 从机输入线 |
MISO | 主机输入 / 从机输出线 |
有的SPI接口芯片带有中断信号INT,有的SPI接口芯片只能作为从机使用,因而只有MISO(主机 》 从机)。
SPI特点:
- 可以同时发出和接收串行数据
- 作主机或从机
- 提供时钟(频率可以调整)
- 发送中断结束标志
- 保护:写冲突保护,总线竞争保护
SPI总线工作方式 |
---|
SP0(使用广泛): |
SP1 |
SP2 |
SP3(使用广泛): |
1.2 STM8 的硬件电路设计
STM8 作为8位单片机, 具有3级流水线哈佛结构,8K存储.
STM8 优点:
- 支持3.3V电平, 可以与FPGA直接连接
- 内置16Mhz晶振
- 支持串行配置
- 最小 TSSOP20 封装, 减少硬件设计及PCB空间
本实验MCU型号: STM8S103F3P6
FPGA--------跳线连接--------MCU
STM8 核心板设计:
STM8 原理图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpSp7vXS-1627885304808)(D:\intelFPGA\typora_picture\微信图片_20210730173530.jpg)]
STM8 引脚 | 对应SPI引脚 |
---|---|
PA3 | SPI_NSS |
PC5 | SPI_SCK |
PC6 | SPI_MOSI |
PC7 | SPI_MISO |
STM8 的SPI接口关于NSS片选有2种模式:
软件模式: 支持多个设备, 按需要选择软件模式, 认为输出NSS.( 为了日后对许多从机的支持, 这里选择PD2 作为 SPI_NSS )
硬件模式: 只能用PA3输出, 只能支持一个设备.
为了便于在调试过程中查看, 还设计了4位 led 指示灯.
1.3 SPI总线协议时序分析
分析STM8的 4 种 SPI 时序:
STM8 自带SPI硬核, 调用SPI 模块非常方便, 而FPGA 中所有的逻辑电路都需要自己设计.
FPGA----MCU 的系统而言, 要确定一个主机一个从机, 由于C语言线程控制的优越性, 而FPGA作为并行运算, 具备硬件加速的特点.
所以, 一般有MCU作为主机, FPGA作为从机.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXQlQEl6-1627885304810)(D:\intelFPGA\typora_picture\SPI协议设计-主从SPI通信框图.png)]
下面研究 STM8 作为主机时的SPI时序图:
STM8 主函数
void main(){
unsigned char i = 0, rxd_char = 0; //i=0; rxd_char=0;
CLK_Configuration(); //时钟配置
SPI_Init(); //SPI初始化
LED_Init(); //LED初始化
while(1){
for(i=0; i<=0xff; i++){ //0 ~ 255循环
rxd_char = SPI_SendByte(i); //接收字符串 = SPI发送的字符串
LED_Data(rxd_char); //LED校验函数
Delay_ms(500); //延时500ms
}
}
}
参数 | 0 | 1 |
---|---|---|
CPOL | SCK空闲时为低电平,有效时从低到高有8个时钟 | SCK空闲时为高电平,有效时从高到低有8个时钟 |
CPHA | 下降沿采样 | 上升沿采样 |
通过排列组合,有SP0,SP1,SP2,SP3 共4种方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ThdrTP5-1627885304811)(D:\intelFPGA\typora_picture\图9 CPOL=CPHA=0的时序图.png)]
时序图分析:
(1)CS低电平有效
(2)主机MOSI在 CS有效后开始输出。从机在CLK上升沿稳态捕获
(3)MISO在 CS 有效后,在SCK 上升沿稳态捕获。
2. SPI总线协议的通信实现
2.1 STM8 的SPI总线收发设计
- SPI 接口的初始化
/*****************SPI初始化****************/
#define SPI_CS_LOW() PD_ODR_ODR2 = 0
#define SPI_CS_HIGH() PD_ODR_ODR2 = 1
void SPI_Init(void){
SPI_CR1 = (0<<7) | (0<<6) | (0<<3) | (1<<2) | (1<<1) | (1<<0); //MSB在前,SPI数据发送频率为8MHz
SPI_CR2 = (0<<7) | (0<<5) | (0<<4) | (0<<2) | (1<<1) | (1<<0); //全双工, 使能软件主模式
SPI_ICR = (0<<7) | (0<<6) | (0<<5) | (0<<4); //禁止所有中断
SPI_CR1 |= (1<<6); //开启SPI模块
PD_DDR_DDR2 = 1; //PD2 设置为输出引脚-----输出数据
PD_CR1_C12 = 1; //设置推挽输出
PD_CR1_C12 = 1; //设置输出频率 1 为10Mhz
SPI_CS_HIGH(); //CS信号高位( 关闭 slave )
}
- SPI 收发函数的实现
/*****************SPI 发送,接收函数****************/
unsigned char SPI_SendByte(unsigned char byte){
SPI_CS_LOW(); //CS信号置0,开始
while( !(SPI_SR & 0x02) ); //等待 发送寄存器 为空
SPI_DR = byte; //将发送的数据写到 ---> 数据寄存器
while( !(SPI_SR & 0x01) ); //等待 接收寄存器 满
SPI_CS_HIGH(); //CS 信号置1,结束
return SPI_DR; //读 数据寄存器
}
- STM8 主函数
/******************STM8 主函数******************/
void main(){
unsigned char i=0, rxd_char=0;
CLK_Configuration(); //时钟配置
SPI_Init(); //SPI初始化
LED_Init(); //LED初始化
while(1){
for(i=0; i<=0xff; i++){ //0 ~ 255循环
rxd_char = SPI_SendByte(i); //接收字符串 = SPI发送的字符串
LED_Data(rxd_char); //LED校验函数
Delay_ms(500); //延时500ms
}
}
2.2 边沿检测电路的FPGA实现
- 边沿检测电路: 检测输入信号 或 PFGA内部逻辑信号的跳变 ( 实现上升沿或下降沿的检测, 捕获得到边沿使能, 来作为时序逻辑的触发信号 )
通过边沿检测实现使能时钟, 提高FPGA时序逻辑稳定性.
- 边沿检测电路的实现:
Trigger_r | Trigger | 边沿检测 |
---|---|---|
低 | 高 | 上升沿 |
高 | 低 | 下降沿 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqrmW48t-1627885304814)(D:\intelFPGA\typora_picture\一级寄存实现的----捕获电路.png)]
一级的 D触发器寄存 在比较时, 当前时刻从外部输入的信号, 与前一时刻的信号不在同一时钟域( FPGA整体逻辑电路的 时钟域 )
二级D触发器作为信号的寄存 ----- 同步时钟设计, 以提高系统可靠性.
- 边沿检测电路的Verilog HDL 实现电路
//---------------------------------------------
// MCU ----(data sync)----> FPGA
reg spi_sck_r0, spi_sck_rl; //spi dafault 1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
spi_sck_r0 <= 0;
spi_sck_r1 <= 0; //data transfer clock
end
else begin
spi_sck_r0 <= spi_sck;
spi_sck_r1 <= spi_sck_r0;
end
end
wire mcu_read_flag = ( ~spi_sck_r1 & spi_sck_r0 )? 1'b1 : 1'b0; //posedge of sck
上一级信号是 : 2级D触发器输出的信号 ----- spi_sck_r1
当前信号是 : D触发器输出的信号 ----- spi_sck_r0
通过两个信号的对比, 可检测出 spi_sck 上升沿 还是 下降沿, 并且输出-------读取使能信号 mcu_read_flag
总结 : 边沿检测, 使能输出.
2.3 SPI通信的数据接收模块设计
- spi 数据接收模块的verilog HDL 实现
新建 : 06_MCU2FPGA_SPI_Test 文件夹
建立 : MCU2FPGA_SPI_Test 工程, 移植全局时钟管理模块( system_index ), LED显示模块( led_index 相关 )
(1) 新建 spi_receiver.v, 信号列表如下
`timescale 1ns / 1ns
//-------------------------------------------//
module spi_receiver(
//global clock
input clk, //时钟
input rst_n, //复位
//mcu spi interface
input spi_cs, //片选
input spi_sck, //数据传输时钟
input spi_mosi,
input spi_miso,
//user interface
output reg rxd_flag, //外部输出的rxd_data 的外部使能信号
output reg [7:0] rxd_data //mcu发送的 串转并 后的 spi数据
);
(2) spi总线接口的--------数据同步模块设计
SPI的数据由MCU输入,与FPGA不在同一个时钟域,同时MCU输入的数据相对于FPGA而言速率很低
采用 2级D触发器寄存输出同步。
代码:
//-------------------------------------------//
//MCU -----> FPGA
reg spi_cs_r0, spi_cs_r1;
reg spi_sck_r0, spi_sck_r1;
reg spi_mosi_r0, spi_mosi_r1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
spi_cs_r0 <= 1, spi_cs_r1 <= 1; //片选使能
spi_sck_r0 <= 0, spi_sck_r1 <= 0; //数据传输时钟
spi_mosi_r0 <= 0, spi_mosi_r1 <= 0; //mosi
end
else begin
spi_cs_r0 <= spi_cs, spi_cs_r1 <= spi_cs_r0; //片选使能
spi_sck_r0 <= spi_sck, spi_sck_r1 <= spi_sck_r0; //数据传输时钟
spi_mosi_r0 <= spi_mosi, spi_mosi_r1 <= spi_mosi_r0; //mosi
end
end
wire mcu_cs = spi_cs_r1;
wire mcu_data = spi_mosi_r1;
wire mcu_read_flag = (~spi_sck_r1 & spi_sck_r0) ? 1'b1 : 1'b0; //sck上升沿
wire mcu_read_done = (~spi_cs_r1 & spi_cs_r0) ? 1'b1 : 1'b0; //cs上升沿
wire 输出的4个信号说明如表:
信号 | 说明 |
---|---|
mcu_cs | 2级D触发器同步后的 spi_cs 信号 |
mcu_data | 2级D触发器同步后的 spi_mosi 信号 |
mcu_read_flag | spi_sck同步边沿检测后的上升沿使能信号 |
mcu_read_done | 8位spi数据传输完毕信号 |
(3) 8位spi数据接收
spi通信协议非常简单,数据的接口可以不用状态机来实现。
设计中主要根据 mcu_read_flag 信号来捕获 SPI 数据,
verilog HDL 代码如下:
//------------------------------------
//sample signal, receive data
reg [3:0] rxd_cnt;
reg [7:0] rxd_data_r;
always @(posedge clk or posedge rst_n) begin
if (!rst) begin
// reset
rxd_cnt <= 0;
rxd_data_r <= 0;
end
//-------------
else if (mcu_cs == 1'b0) begin
if(mcu_read_flag) begin //sck上升沿
rxd_data_r[[3'd7 - rxd_cnt[2:0]] <= mcu_data;
rxd_cnt <= rxd_cnt + 1'b1; //计数器加1,0-7-8
end
else begin
rxd_cnt <= rxd_cnt;
rxd_data_r <= rxd_data_r;
end
end
//-------------
else begin
rxd_cnt <= 0;
rxd_data_r <= rxd_data_r;
end
end
设计电路时,首先判断 mcu_cs 是否有效,当 mcu_cs 无效,即 mcu_cs = 1, FPGA挂起,
否则根据使能信号 mcu_read_flag 来捕获 8位数据。8位数据的捕获根据 rxt_cnt 的计数来实现。
当mcu_read_flag 无效时,数据保持不变。
(4) 捕获完成后使能信号的输出
如 矩阵键盘检测电路 一样,在捕获到外部数据后, 为了便于后端模块的响应,人为输出一个读取完成后的触发信号,作为rxd_data 读取的激励。
当 mcu_cs 从有效到无效时,即完成一次 SPI 数据通信( 捕获到 mcu_read_down 有效 )。
//------------------------------------
//output_spi_receive_data_and_receive_flag
always @(posedge clk or negedge rst_n) begin
if (!rst) begin
// reset
rxd_flag <= 0;
rxd_data <= 0;
end
else if (mcu_read_done) begin //mcu_read_done有效时读取 串转并 后的数据rxd_data_r
rxd_flag <= 1'b1; //同步输出了并行数据捕获的 rxd_flag, 这2个信号同步输出,作为外部信号的激励。
rxd_data <= rxd_data_r;
end
else begin
rxd_flag <= 0;
rxd_data <= rxd_data;
end
end
- modelsim仿真(未完待续。。。)