赞
踩
源代码(来自Alinx FL9267参考例程):
该模块完成了SPI单个字节的传输功能,可设置时钟分频、时钟极性、时钟相位。使用时,可以直接修改程序将该模块扩展至三个字节;也可以再写一个模块,完成三个字节读写的控制。
module spi_master ( input sys_clk, input rst, output nCS, //chip select (SPI mode) output DCLK, //spi clock output MOSI, //spi data output input MISO, //spi input input CPOL, input CPHA, input nCS_ctrl, input[15:0] clk_div, input wr_req, output wr_ack, input[7:0] data_in, output[7:0] data_out ); localparam IDLE = 0; localparam DCLK_EDGE = 1; localparam DCLK_IDLE = 2; localparam ACK = 3; localparam LAST_HALF_CYCLE = 4; localparam ACK_WAIT = 5; reg DCLK_reg; reg[7:0] MOSI_shift; reg[7:0] MISO_shift; reg[2:0] state; reg[2:0] next_state; reg [15:0] clk_cnt; reg[4:0] clk_edge_cnt; assign MOSI = MOSI_shift[7]; assign DCLK = DCLK_reg; assign data_out = MISO_shift; assign wr_ack = (state == ACK); assign nCS = nCS_ctrl; always@(posedge sys_clk or posedge rst) begin if(rst) state <= IDLE; else state <= next_state; end always@(*) begin case(state) IDLE: if(wr_req == 1'b1) next_state <= DCLK_IDLE; else next_state <= IDLE; DCLK_IDLE: //half a SPI clock cycle produces a clock edge if(clk_cnt == clk_div) next_state <= DCLK_EDGE; else next_state <= DCLK_IDLE; DCLK_EDGE: //a SPI byte with a total of 16 clock edges if(clk_edge_cnt == 5'd15) next_state <= LAST_HALF_CYCLE; else next_state <= DCLK_IDLE; //this is the last data edge LAST_HALF_CYCLE: if(clk_cnt == clk_div) next_state <= ACK; else next_state <= LAST_HALF_CYCLE; //send one byte complete ACK: next_state <= ACK_WAIT; //wait for one clock cycle, to ensure that the cancel request signal ACK_WAIT: next_state <= IDLE; default: next_state <= IDLE; endcase end always@(posedge sys_clk or posedge rst) begin if(rst) DCLK_reg <= 1'b0; else if(state == IDLE) DCLK_reg <= CPOL; else if(state == DCLK_EDGE) DCLK_reg <= ~DCLK_reg;//SPI clock edge end //SPI clock wait counter always@(posedge sys_clk or posedge rst) begin if(rst) clk_cnt <= 16'd0; else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE) clk_cnt <= clk_cnt + 16'd1; else clk_cnt <= 16'd0; end //SPI clock edge counter always@(posedge sys_clk or posedge rst) begin if(rst) clk_edge_cnt <= 5'd0; else if(state == DCLK_EDGE) clk_edge_cnt <= clk_edge_cnt + 5'd1; else if(state == IDLE) clk_edge_cnt <= 5'd0; end //SPI data output always@(posedge sys_clk or posedge rst) begin if(rst) MOSI_shift <= 8'd0; else if(state == IDLE && wr_req) MOSI_shift <= data_in; else if(state == DCLK_EDGE) if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b1) MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]}; else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] == 1'b0)) MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]}; end //SPI data input always@(posedge sys_clk or posedge rst) begin if(rst) MISO_shift <= 8'd0; else if(state == IDLE && wr_req) MISO_shift <= 8'h00; else if(state == DCLK_EDGE) if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b0) MISO_shift <= {MISO_shift[6:0],MISO}; else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1)) MISO_shift <= {MISO_shift[6:0],MISO}; end endmodule
仿真结果:
ZYNQ-7000共有54个MIO引脚,其引脚是固定的;还有64个EMIO接口,可以通过修改约束文件连接到PL端的引脚。
ZYNQ-7000 SOC中SPI 控制器的特性如下。
值得注意的是,SPI控制器的总线可以连接到MIO引脚,也可以连接到EMIO接口,但是前者的SCLK最大速率可达到50 MHz,后者SCLK速率最高支持25 MHz。
Vivado Block Diagram中的配置如下。
在ZYNQ7 Processing System IP核中勾选SPI0,并根据开发板原理图选择IO,可选固定MIO或EMIO。其他使用PS端的基本配置和建立工程步骤就不详细展开了。
ad936x_spi.c
#include "ad936x_spi.h" XSpiPs_Config *spi_config; XSpiPs spi_instance; int32_t spi_init(uint32_t device_id, uint8_t clk_pha, uint8_t clk_pol) { uint32_t base_addr = 0; uint32_t spi_options = 0; spi_config = XSpiPs_LookupConfig(device_id); base_addr = spi_config->BaseAddress; XSpiPs_CfgInitialize(&spi_instance, spi_config, base_addr); spi_options = XSPIPS_MASTER_OPTION | (clk_pol ? XSPIPS_CLK_ACTIVE_LOW_OPTION : 0) | (clk_pha ? XSPIPS_CLK_PHASE_1_OPTION : 0) | XSPIPS_FORCE_SSELECT_OPTION; XSpiPs_SetOptions(&spi_instance, spi_options); XSpiPs_SetClkPrescaler(&spi_instance, XSPIPS_CLK_PRESCALE_32); return 0; } int32_t spi_read(uint8_t *data, uint8_t bytes_number) { XSpiPs_SetSlaveSelect(&spi_instance, 0);//spi0 XSpiPs_PolledTransfer(&spi_instance, data, data, bytes_number); return 0; } int spi_write_then_read(unsigned char *txbuf, unsigned n_tx, unsigned char *rxbuf, unsigned n_rx) { uint8_t buffer[20] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t byte; for(byte = 0; byte < n_tx; byte++) { buffer[byte] = (unsigned char)txbuf[byte]; } spi_read(buffer, n_tx + n_rx); for(byte = n_tx; byte < n_tx + n_rx; byte++) { rxbuf[byte - n_tx] = buffer[byte]; } return 0; } uint8_t ad936x_spi_read(uint16_t addr) { uint8_t txbuf[2]; uint8_t rxbuf[1]; uint8_t addr_msb = addr>>8; uint8_t addr_lsb = (uint8_t)addr; txbuf[0] = 0x7F & addr_msb; txbuf[1] = addr_lsb; spi_write_then_read(txbuf, 2, rxbuf, 1); return rxbuf[0]; } void ad936x_spi_write(uint16_t addr, uint8_t data) { uint8_t txbuf[3]; uint8_t addr_msb = addr>>8; uint8_t addr_lsb = (uint8_t)addr; txbuf[0] = 0x80|addr_msb; txbuf[1] = addr_lsb; txbuf[2] = data; spi_write_then_read(txbuf, 3, NULL, 0); }
ad936x_spi.h
#ifndef SRC_AD936X_SPI_H_ #define SRC_AD936X_SPI_H_ #include <stdint.h> #include "stdio.h" #include "xparameters.h" #include "xspips.h" #include "sleep.h" #define SPI_DEVICE_ID XPAR_PS7_SPI_0_DEVICE_ID int32_t spi_init(uint32_t device_id, uint8_t clk_pha, uint8_t clk_pol); int32_t spi_read(uint8_t *data, uint8_t bytes_number); int spi_write_then_read(unsigned char *txbuf, unsigned n_tx, unsigned char *rxbuf, unsigned n_rx); uint8_t ad936x_spi_read(uint16_t addr); void ad936x_spi_write(uint16_t addr, uint8_t data); #endif /* SRC_AD936X_SPI_H_ */
ZYNQ-7000 SOC中GPIO的特性和框图如下。
在ZYNQ7 Processing System IP核中勾选GPIO,其IO默认MIO,若想使用EMIO连接到PL的引脚,可勾选EMIOGPIO(Width),并选择宽度,即需要使用多少个引脚。
用软件方式模拟实现SPI,只需要知道GPIO是如何输入输出的。
只给出初始化函数和GPIO输入输出操作函数,其他程序段参考下一节AXI_GPIO的内容。
#define GPIO_DEVICE_ID XPAR_PS7_GPIO_0_DEVICE_ID //MIO 0-53 #define MIO_LED 15 //EMIO 54-117 #define SPI_SCLK_Pins 54 #define SPI_MOSI_Pins 55 #define SPI_MISO_Pins 56 #define SPI_CS_Pins 57 #define RESET_Pins 58 XGpioPs_Config *gpio_config; XGpioPs gpio_instance; void gpio_init(uint32_t device_id) { gpio_config = XGpioPs_LookupConfig(device_id); XGpioPs_CfgInitialize(&gpio_instance, gpio_config, gpio_config->BaseAddr); gpio_direction(MIO_LED, 1); gpio_direction(SPI_SCLK_Pins, 1); gpio_direction(SPI_MOSI_Pins, 1); gpio_direction(SPI_MISO_Pins, 0); gpio_direction(SPI_CS_Pins, 1); gpio_direction(RESET_Pins, 1); gpio_set_value(GPIO_RESET, 0); gpio_set_value(STA_LED, 0); } void gpio_direction(uint8_t pin, uint8_t direction) { //0 input, 1 output XGpioPs_SetDirectionPin(&gpio_instance, pin, direction); XGpioPs_SetOutputEnablePin(&gpio_instance, pin, 1); } void gpio_set_value(uint8_t pin, uint8_t value) { XGpioPs_WritePin(&gpio_instance, pin, value); } uint8_t gpio_get_value(uint8_t pin) { return (uint8_t)XGpioPs_ReadPin(&gpio_instance, pin); }
AXI-GPIO IP核可以说PS端GPIO的拓展,可以直接编写HDL程序控制该IP核,这里只讲解如何在PS端通过C语言程序控制,其本质也是对AXI总线的寄存器的读写。
使用AXI_GPIO的难点是如何控制将单个引脚的控制转换成AXI总线的地址读写操作。
#define SPI_SCLK_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_SCLK_Pins))
#define SPI_SCLK_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_SCLK_Pins)))
#define SPI_MOSI_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_MOSI_Pins))
#define SPI_MOSI_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_MOSI_Pins)))
#define SPI_CS_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_CS_Pins))
#define SPI_CS_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_CS_Pins)))
#define RESET_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<RESET_Pins))
#define RESET_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<RESET_Pins)))
Vivado Block Diagram如下:
ad936x_spi.c
#include "stdio.h" #include "xparameters.h" #include "xgpiops.h" #include "xgpio.h" #include "ad9363_spi.h" XGpioPs gpiops_inst; //PS 端 GPIO 驱动实例 XGpioPs_Config *gpiops_cfg_ptr; //PS 端 GPIO 配置信息 XGpio axi_gpio1_inst; //PL 端 AXI GPIO1 驱动实例 XGpio axi_gpio2_inst; //PL 端 AXI GPIO2 驱动实例 void PS_AXI_GPIO_Init(void) { //初始化 PS GPIO gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID); XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr); //初始化 PL AXI_GPIO XGpio_Initialize(&axi_gpio1_inst, AXI_GPIO_ID); XGpio_Initialize(&axi_gpio2_inst, AXI_GPIO_ID); //配置 PS GPIO XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1); //设置 PS GPIO 为输出 XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED, 1); //使能 LED 输出 PS_LED_H; //配置PL AXI_GPIO XGpio_SetDataDirection(&axi_gpio1_inst, 1, 0); //设置 AXI GPIO 通道 1 为输出 XGpio_SetDataDirection(&axi_gpio2_inst, 2, 1); //设置 AXI GPIO 通道 2 为输入 SPI_SCLK_L; SPI_MOSI_L; SPI_CS_H; RESET_H; } void ad9363_SPI_Write(u16 addr, u8 data) { SPI_CS_L; ad9363_SPI_Write_Byte((u8)(addr>>8)+0x80); ad9363_SPI_Write_Byte((u8)(addr)); ad9363_SPI_Write_Byte(data); SPI_CS_H; SPI_MOSI_L; } void ad9363_SPI_Write_Byte(u8 wdata) { u8 i = 0; for( i = 0; i< 8; i++){ SPI_SCLK_H; if(wdata&0x80) SPI_MOSI_H; else SPI_MOSI_L; SPI_SCLK_L; SPI_MOSI_L; wdata <<= 1; } } u8 ad9363_SPI_Read(u16 addr) { u8 rdata; SPI_CS_L; ad9363_SPI_Write_Byte((u8)(addr>>8)); ad9363_SPI_Write_Byte((u8)(addr)); rdata = ad9363_SPI_Read_Byte(); SPI_CS_H; return rdata; } u8 ad9363_SPI_Read_Byte(void) { u8 i; u8 rdata; for(i = 0; i<8; i++){ SPI_SCLK_H; SPI_SCLK_L; if(XGpio_DiscreteRead(&axi_gpio2_inst, 2)&0x00001) rdata |= 0x80>>i; } return rdata; } void ps_LED(u8 status) { if(status) PS_LED_H; else PS_LED_L; }
ad936x_spi.h
#ifndef SRC_AD9363_SPI_AD9363_SPI_H_ #define SRC_AD9363_SPI_AD9363_SPI_H_ #include "stdio.h" #include "xparameters.h" #include "xgpiops.h" #include "xgpio.h" #define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS 端 GPIO 器件 ID #define AXI_GPIO_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL 端 AXI GPIO 器件 ID //PS GPIO #define MIO_LED 15 //PS LED 连接到 MIO0 //Channel 1 输出 #define SPI_SCLK_Pins 0 //PL SPI SCLK GPIO 通道1 #define SPI_MOSI_Pins 1 #define SPI_CS_Pins 2 #define RESET_Pins 3 //Channel 2 输入 #define SPI_MISO_Pins 1 //PS GPIO 输出 #define PS_LED_H XGpioPs_WritePin(&gpiops_inst, MIO_LED, 1) #define PS_LED_L XGpioPs_WritePin(&gpiops_inst, MIO_LED, 0) //通道1 GPIO 输出 #define SPI_SCLK_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_SCLK_Pins)) #define SPI_SCLK_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_SCLK_Pins))) #define SPI_MOSI_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_MOSI_Pins)) #define SPI_MOSI_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_MOSI_Pins))) #define SPI_CS_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<SPI_CS_Pins)) #define SPI_CS_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<SPI_CS_Pins))) #define RESET_H XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)|(0x0001<<RESET_Pins)) #define RESET_L XGpio_DiscreteWrite(&axi_gpio1_inst, 1, XGpio_DiscreteRead(&axi_gpio1_inst, 1)&(~(0x0001<<RESET_Pins))) //通道2 GPIO 输入 //#define SPI_MISO_H XGpio_DiscreteWrite(&axi_gpio2_inst, 2, XGpio_DiscreteRead(&axi_gpio2_inst, 2)|(0x0001<<SPI_MISO_Pins)) //#define SPI_MISO_L XGpio_DiscreteWrite(&axi_gpio2_inst, 2, XGpio_DiscreteRead(&axi_gpio2_inst, 2)&(~(0x0001<<SPI_MISO_Pins))) void PS_AXI_GPIO_Init(void); void ad9363_SPI_Write(u16 addr, u8 data); void ad9363_SPI_Write_Byte(u8 wdata); u8 ad9363_SPI_Read(u16 addr); u8 ad9363_SPI_Read_Byte(void); void ps_LED(u8); #endif /* SRC_AD9363_SPI_AD9363_SPI_H_ */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。