STM32与FPGA的FSMC通讯,STM32作为主机,FPGA作为从机。本文在STM32采用FSMC拓展SRAM的程序基础上编写FPGA代码及STM32访问FPGA的代码。将FPGA视为SRAM进行STM32与FPGA的通讯。
FSMC译为灵活的静态存储控制器,STM32F1系列芯片使用FSMC外设来管理扩展的存储器。
FSMC引脚名称 | 对应的FPGA引脚名 | 说明 |
---|---|---|
FSMC_A[2:0] | addr[2:0] | 行地址信号 |
FSMC_D[15:0] | data[15:0] | 数据线 |
FSMC_NWE | WR | 写入使能 |
FSMC_NOE | RD | 读出使能 |
FSMC_NE[1:4] | CS | 片选信号 |
本实验采用FSMC_NE[1]。
2.存储器控制器:NOR/PSRAM/SRAM 设备使用相同的控制器,NAND/PC 卡设备使用相同的控制器。
控制 SRAM 的有以下3种寄存器:
FSMC_BCR 控制寄存器可配置要控制的存储器类型、数据线宽度以及信号有效极性能参数。
FMC_BTR 时序寄存器用于配置 SRAM 访问时的各种时间延迟,如数据保持间、地址保持时间等。
FMC_BWTR 写时序寄存器它专门用于控制写时序的时间参数。
每种寄存器都有 4 个,分别对应于 4 个不同的存储区域.
3、时钟控制逻辑
FSMC 外设挂载在 AHB总线上,时钟信号来自于 HCLK(默认 72MHz),控制器的同步时钟输出就是由它分频得到。本实验不采用同步时钟信号,无需时钟分频。
FSMC连接好外部存储器并初始化后,就可以直接通过访问地址来读写数据。因为外接的存储单元是映射到STM32的内部寻址空间的。FSMC的地址映射如下所示:
FSMC将整个 External RAM 存储区域分成了 4 个 Bank 区域,并分配了地址范围及适用的存储器类型,每个Bank的内部又分为4个小块。本实验将FPGA视为SRAM,则只能使用Bank1中的地址,Bank1内部四块地址分配如下图:
FSMC外设支持输出多种不同的时序以便控制不同的存储器,共有ABCD四种模式,下图为FSMC模式A的读时序:
FSMC模式A的写时序如下:
STM32中初始化FSMC外设代码如下:
/**
* @brief 初始化FSMC外设
* @param None.
* @retval None.
*/
void FSMC_SRAM_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;
/*初始化SRAM相关的GPIO*/
SRAM_GPIO_Config();
/*使能FSMC外设时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
//地址建立时间(ADDSET)为1个HCLK 1/72M=14ns
readWriteTiming.FSMC_AddressSetupTime = 0x00;
//地址保持时间(ADDHLD)模式A未用到
readWriteTiming.FSMC_AddressHoldTime = 0x00;
//数据保持时间(DATAST)+ 1个HCLK = 3/72M=42ns(对EM的SRAM芯片)
readWriteTiming.FSMC_DataSetupTime = 0x02;
//设置总线转换周期,仅用于复用模式的NOR操作
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
//设置时钟分频,仅用于同步类型的存储器
readWriteTiming.FSMC_CLKDivision = 0x00;
//数据保持时间,仅用于同步型的NOR
readWriteTiming.FSMC_DataLatency = 0x00;
//选择匹配SRAM的模式
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;
// 选择FSMC映射的存储区域: Bank1 sram2
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
//设置地址总线与数据总线是否复用,仅用于NOR
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
//设置要控制的存储器类型:SRAM类型
FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;
//存储器数据宽度:16位
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
//设置是否使用突发访问模式,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;
//设置是否使能等待信号,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
//设置等待信号的有效极性,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
//设置是否支持把非对齐的突发操作,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
//设置等待信号插入的时间,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
//存储器写使能
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
//不使用等待信号
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
// 不使用扩展模式,读写使用相同的时序
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
//突发写操作
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
//读写时序配置
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
//读写同样时序,使用扩展模式时这个配置才有效
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); //初始化FSMC配置
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 使能BANK
}
配置好FSMC后,,可直接采用绝对地址访问FPGA内部的寄存器数值。本实验才采用的是FSMC_NE[1],对应的内核地址为((uint32_t)(0x60000000)),宏定义如下:
#define Bank1_SRAM1_ADDR ((uint32_t)(0x60000000))
*( uint16_t*) (Bank1_SRAM1_ADDR ) = (uint16_t)0xAA;//将0XAA写入FPGA的ARM_FPGA_REG0中
测试程序
/**
* @brief 测试SRAM是否正常
* @param None
* @retval 正常返回1,异常返回0
*/
uint8_t SRAM_Test(void)
{
/*写入数据计数器*/
uint32_t counter=0;
/* 16位的数据 */
uint16_t uhWritedata_16b = 0, uhReaddata_16b = 0;
SRAM_INFO("正在检测SRAM,以16位的方式读写sram...");
/*按16位格式读写数据,并检测*/
/* 把SRAM数据全部重置为0 */
for (counter = 0x00; counter < FPGA_3_16_SIZE/2; counter++)
{
*(__IO uint16_t*) (Bank1_SRAM1_ADDR + 2*counter) = (uint16_t)0x00;
}
/* 向整个SRAM写入数据 16位 */
for (counter = 0; counter < FPGA_3_16_SIZE/2; counter++)
{
*(__IO uint16_t*) (Bank1_SRAM1_ADDR + 2*counter) = (uint16_t)(uhWritedata_16b + counter);
//SRAM_INFO("%d",*(__IO uint16_t*) (Bank1_SRAM1_ADDR + 2*counter));
}
SRAM_INFO("counter = %d",counter);
/* 读取 SRAM 数据并检测*/
for(counter = 0; counter<FPGA_3_16_SIZE/2;counter++ )
{
uhReaddata_16b = *(__IO uint16_t*)(Bank1_SRAM1_ADDR + 2*counter); //从该地址读出数据
if(uhReaddata_16b != (uint16_t)(uhWritedata_16b + counter)) //检测数据,若不相等,跳出函数,返回检测失败结果。
{
SRAM_ERROR("16位数据读写错误!");
return 0;
}
}
SRAM_INFO("SRAM读写测试正常!");
/*检测正常,return 1 */
return 1;
}
主函数
int main(void)
{
LED_GPIO_Config();
//串口初始化
USART_Config();
//初始化外部SRAM
FSMC_SRAM_Init();
SysTick_Init();
printf ( "\r\n野火外部 SRAM 测试\r\n" );
/*蓝灯亮,表示正在读写SRAM测试*/
LED_RED;
/*对SRAM进行读写测试,检测SRAM是否正常*/
if(SRAM_Test()==1)
{
//测试正常 绿灯亮
LED_GREEN;
}
else
{
//测试失败 红灯亮
LED_RED;
}
/*指针方式访问SRAM*/
{
uint32_t temp;
printf("\r\n指针方式访问SRAM\r\n");
/*写/读 16位数据*/
*( uint16_t*) (Bank1_SRAM1_ADDR ) = (uint16_t)0xAA;
printf("指针访问SRAM,写入数据0xAA \r\n");
temp = *( uint16_t*) (Bank1_SRAM1_ADDR );
printf("读取数据:0x%X\r\n",temp);
}
/*绝对定位方式访问SRAM,这种方式必须定义成全局变量*/
{
testValue = 0xaa;
printf("\r\n绝对定位访问SRAM,写入数据0xaa,读出数据0x%X,变量地址为%X\r\n",testValue,(uint32_t )&testValue);
}
}
本实验将FPGA视为3根地址线、16根数据线的SRAM,即FPGA内部具有8个存储单元,每个从存储单元可存放16位数据。可视为存储大小为16B的SRAM。
所以FPGA内部要设置8个16位的寄存器,用于存放数据。
代码如下:
/* STM32与FPGA之间的FSMC通讯 */
module STM32_FPGA(
input sys_clk,
input sys_rst_n,
input [2:0] WR,
inout [15:0] data,
input FPGA_CS0,//FPGA片选段
input RD,//ARM从FPGA读数据
input WR,//ARM写数据到FPGA
output led,
output led1
);
reg [24:0] cnt ;
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
assign led = cnt[24];
//FPGA内部8个数据存放的寄存器
reg [15:0] ARM_FPGA_REG0;
reg [15:0] ARM_FPGA_REG1;
reg [15:0] ARM_FPGA_REG2;
reg [15:0] ARM_FPGA_REG3;
reg [15:0] ARM_FPGA_REG4;
reg [15:0] ARM_FPGA_REG5;
reg [15:0] ARM_FPGA_REG6;
reg [15:0] ARM_FPGA_REG7;
wire rd_en;
wire wr_en;
assign wr_en = ~FPGA_CS0 && ~WR;//写使能
assign rd_en = ~FPGA_CS0 && ~RD;//读使能
reg [15:0] data_reg;
//根据地址线,ARM从FPGA中读取对应寄存器数据放在寄存器data_reg中
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 16'd0;
else if(rd_en)
begin
case(addr[2:0])
3'd0 : data_reg <= ARM_FPGA_REG0;
3'd1 : data_reg <= ARM_FPGA_REG1;
3'd2 : data_reg <= ARM_FPGA_REG2;
3'd3 : data_reg <= ARM_FPGA_REG3;
3'd4 : data_reg <= ARM_FPGA_REG4;
3'd5 : data_reg <= ARM_FPGA_REG5;
3'd6 : data_reg <= ARM_FPGA_REG6;
3'd7 : data_reg <= ARM_FPGA_REG7;
default: ;
endcase
end
//根据地址线,ARM将都数据写入到对应的就寄存器中
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
ARM_FPGA_REG0 <= 16'd0;
ARM_FPGA_REG1 <= 16'd0;
ARM_FPGA_REG2 <= 16'd0;
ARM_FPGA_REG3 <= 16'd0;
ARM_FPGA_REG4 <= 16'd0;
ARM_FPGA_REG5 <= 16'd0;
ARM_FPGA_REG6 <= 16'd0;
ARM_FPGA_REG7 <= 16'd0;
end
else if(wr_en)
case(addr[2:0])
3'd0 : ARM_FPGA_REG0 <= data;
3'd1 : ARM_FPGA_REG1 <= data;
3'd2 : ARM_FPGA_REG2 <= data;
3'd3 : ARM_FPGA_REG3 <= data;
3'd4 : ARM_FPGA_REG4 <= data;
3'd5 : ARM_FPGA_REG5 <= data;
3'd6 : ARM_FPGA_REG6 <= data;
3'd7 : ARM_FPGA_REG7 <= data;
default: ;
endcase
assign data = rd_en ? data_reg : 16'hzzzz;//将数据传给ARM
assign led1 = (ARM_FPGA_REG0 == 16'haa)? 1'b1:1'b0;//判断接收到的数据是否正确(STM32发送的数据是16'haa)
endmodule
STM32依次给FPGA发送数据0xaa并接收FPGA发送过来的数据,通过串口打印出。FPGA接收的数据为0xaa,则点亮led1灯。
实验结果如图所示: