电子说
SPI简介
SPI为串行外设接口,全称Serial Peripheral interface,是一种全双工、同步的通信总线,广泛用于不同设备之间的板级通讯。
MM32F0140的SPI支持接收和发送1 ~ 32位数据同时进行,主模式最大速率24Mbps,从模式最大速率12Mbps,支持一个主机与多个从机操作,支持DMA操作。
数据通信
在进行SPI数据通信时,通常由MOSI、MISO、SCK、NSS四个管脚与外部器件相连。如图1所示,MOSI管脚将来自主设备的数据输入到从设备,MISO管脚将从设备响应的数据传入主设备,从设备通过SCK管脚获得主设备提供的时钟信号,使发送和接收部分使用相同的时钟,保证数据传输的可靠性。NSS管脚进行从设备选择,使主设备可以和某个从设备一对一单独通信。
主设备数据输入(MISO)
MISO为主设备输入从设备输出管脚,传输方向为从设备发送到主设备。
主设备数据输出(MOSI)
MOSI为主设备输出从设备输入管脚,传输方向为主设备发送到从设备。
时钟(SCK)
SCK为串口时钟,控制数据交换的速率,由主设备产生,通过SCK引脚传输供从设备使用。
片选(NSS)
NSS为从设备选择管脚,SPI通过控制片选管脚NSS来控制多个从设备。当NSS引脚功能被激活后,配置作为主设备的SPI进入主模式,将会拉低NSS引脚,其余连接到主设备NSS的SPI设备由于检测到了NSS拉低的信号,会自动进入从设备模式。
图1.SPI数据通信
数据传输时序
SPI可以通过配置时钟极性(CPOL)与相位(CPHA)选择四种不同的数据传输时序,即四种工作方式。
时钟极性(CPOL)
时钟极性是SCK在空闲时保持的电平状态,当CPOL配置为0时,SCK在空闲状态为低电平,即两次传输之间为低电平;当CPOL配置为1时,SCK在空闲状态为高电平,即两次传输之间为高电平。
时钟相位(CPHA)
时钟相位用于决定采样时刻,当CPHA配置为0时,第一个数据位采样从第一个时钟边沿开始;当CPHA配置为1时,第一个数据位采样从第二个时钟边沿开始;对数据进行边沿采样需要CPOL与CPHA的共同配置决定。
SCK低电平空闲,第一个沿采样
当CPOL=0且CPHA=0,第一位数据位在SCK时钟的第一个时钟上升沿被采样,如图2所示。
图2.SCK低电平空闲且第一个沿采样
SCK高电平空闲,第一个沿采样
当CPOL=1且CPHA=0,第一位数据位的SCK时钟的第一个时钟下降沿被采样,如图3所示。
图3.SCK高电平空闲且第一个沿采样
SCK低电平空闲,第二个沿采样
当CPOL=0且CPHA=1,第一个数据位在SCK时钟的第二个时钟下降沿被采样,如图4所示。
图4.SCK低电平空闲且第二个沿采样
SCK高电平空闲,第二个沿采样
当CPOL=1且CPHA=1,第一个数据位在SCK时钟的第二个时钟上升沿被采样,如图5所示。
图5.SCK高电平空闲且第二个沿采样
SPI配置
主模式
通过配置波特率发生器(SPI_I2S_SPBREG)设定串行时钟波特率,公式为:波特率=fpclk/SPBRG (fpclk是APB时钟频率)。配置通用控制寄存器(SPI_I2S_CCTL)的SPI数据宽度位(SPILEN),决定数据帧的长度是7位还是8位;配置时钟相位选择位(CPHA)与时钟极性标志位(CPOL),确定时序模式,为保证数据正常传输,主从设备的时序模式应保持配置一致;配置LSB在前使能位(LSBFE),决定数据位的输出顺序。操作全局控制寄存器(SPI_I2S_GCTL)的DW8_32位进行发送和接收数据寄存器有效数据选择,可配置为只有低8位有效或32位数据都有效;操作主机模式位(MODE)为1,选择主机模式;配置SPI/I2S选择位(SPIEN)为1,使能SPI。
若只接收而不发送数据,则配置接收数据个数寄存器(SPI_I2S_RXDNR),定义下次接收过程中需要接收字节的个数。
从模式
配置通用控制寄存器(SPI_I2S_CCTL)的LSB在前使能位(LSBFE),决定数据位的输出顺序是从最低有效位到最高有效位或从最高有效位到最低有效位;配置SPI数据宽度位(SPILEN),决定数据帧的长度是7位还是8位;配置时钟相位选择位(CPHA)与时钟极性标志位(CPOL),决定时序模式。配置全局控制寄存器(SPI_I2S_GCTL)的主机模式位(MODE)为0,选择从机模式;配置SPI/I2S选择位(SPIEN)为1,使能SPI。
数据发送
主模式
将需要发送的数据写入发送数据寄存器(SPI_I2S_TXREG),该寄存器的有效位由全局控制器(SPI_I2S_GCTL)的DW8_32位控制。在发送第一个数据位时,整个数据被传输到移位寄存器,后续数据通过移位寄存器串行输出到MOSI引脚。当中断状态寄存器(SPI_I2S_INTSTAT)的发送缓冲器有效中断标志位(TX_INTF)被置1,数据已从发送缓冲器被传输到移位寄存器。
从模式
当从设备收到SCK传来的时钟信号,同时接收到MOSI引脚传输的第一个数据位,从设备开始发送,第一个位被发送到MISO引脚,其余bit位被传输到移位寄存器,通过移位寄存器将数据串行发送。当中断状态寄存器(SPI_I2S_INTSTAT)的发送缓冲器有效中断标志位(TX_INTF)被置1,表示第一位已发送,其余位被传输到移位寄存器。
数据接收
主模式
从MISO引脚接收数据,数据通过移位寄存器,在最后一个采样时钟边沿后,数据字节被传输到接收缓冲器中。当中断状态寄存器(SPI_I2S_INTSTAT)的接收端数据有效中断标志位(RX_INTF)置1,数据接收完成,主模式下不再发送时钟信号。
从模式
从MOSI引脚接收数据,数据通过移位寄存器,在最后一个采样时钟边沿后,数据字节被传输到接收缓冲器中。当中断状态寄存器(SPI_I2S_INTSTAT)的接收端数据有效中断标志位(RX_INTF)置1,数据接收完成。
实验
本实验为回环测试,通过使用杜邦线连接SPI的MISO与MOSI引脚,实现数据的发送与接收。配置SPI主机,SPI进行一次数据发送与接收并对发送与接收信息进行验证,并通过串口打印传输情况,若有发送与接收数据不同的情况,串口打印出错信息与出错个数,若验证成功则打印"spi loopback xfer done."。
启用外设时钟 enable_clock()
实验使用SPI1,且需要通过串口打印实验现象,因此需启用SPI1与UART的外设时钟。
{ /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable SPI1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_SPI1; }
配置引脚 pin_init()
配置SPI的NSS(PA4)、MOSI(PA7)、MISO(PA6)、SCK(PA5)引脚,因为实验现象通过串口显示,所以配置UART的TX(PA9)与RX(PA10)引脚。
void pin_init() { /* Setup NSS(PA4). */ GPIOA->CHL = ~GPIO_CRL_MODE4_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE4_SHIFT); /* PA4 multiplexed push-pull output. */ GPIOA->AFRL = ~GPIO_AFRL_AFR_MASK; GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE4_SHIFT); /* Use AF0. */ /* Setup MOSI(PA7). */ GPIOA->CHL = ~GPIO_CRL_MODE7_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE7_SHIFT); /* PA7 multiplexed push-pull output. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE7_SHIFT); /* Use AF0. */ /* Setup MISO(PA6). */ GPIOA->CHL = ~GPIO_CRL_MODE6_MASK; GPIOA->CHL |= (GPIO_PinMode_In_Floating << GPIO_CRL_MODE6_SHIFT); /* PA6 floating input. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE6_SHIFT); /* Use AF0. */ /* Setup SCK(PA5). */ GPIOA->CHL = ~GPIO_CRL_MODE5_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE5_SHIFT); /* PA5 floating input. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE5_SHIFT); /* Use AF0. */ /* Setup PA9, PA10. */ GPIOA->CRH = ~GPIO_CRH_MODE9_MASK; GPIOA->CRH |= (GPIO_PinMode_AF_PushPull << GPIO_CRH_MODE9_SHIFT); /* PA9 multiplexed push-pull output. */ GPIOA->AFRH = ~GPIO_AFRH_AFR_MASK; GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* Use AF1. */ GPIOA->CRH = ~GPIO_CRH_MODE10_MASK; GPIOA->CRH |= (GPIO_PinMode_In_Floating << GPIO_CRH_MODE10_SHIFT); /* PA10 floating input. */ GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); /* Use AF1. */ }
UART初始化 uart_init()
初始化UART,配置时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR = ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR = ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
SPI初始化 spi_init()
操作全局控制寄存器(SPI_I2S_GCTL)的MODE位,配置SPI为主模式,操作波特率发生器(SPI_I2S_SPBREG)配置波特率为400KHz,总线时钟频率为48MHz,操作通用控制寄存器(SPI_I2S_CCTL)的CPOL位与CPHA位配置通信模式,操作LSB在前使能位(LSBFE)令数据发送或接收最高位在前,操作全局控制寄存器对发送和接收数据寄存器有效数据进行选择(DW8_32),配置为只有低8位有效,设置NSS位置1,使硬件控制主模式下的NSS输出,配TXEN位与RXEN位置1,使能发送与接收;置SPI/I2S选择位(SPIEN)为1,使能SPI。
void spi_init() { /* Master. */ SPI1->GCTL = SPI_I2S_GCTL_MODE_MASK; /* Master mode. */ /* XferMode. */ SPI1->GCTL |= (SPI_I2S_GCTL_RXEN_MASK | SPI_I2S_GCTL_TXEN_MASK); /* Enable TX and RX. */ /* AutoCS. */ SPI1->GCTL |= SPI_I2S_GCTL_NSS_MASK; /* NSS select signal that from hardware. */ /* BaudRate. */ SPI1->SPBRG = 120u; /* SPBRG = fpclk / baudrate = 48000000 / 400000 = 120. */ SPI1->CCTL = ~(SPI_I2S_CCTL_TXEDGE_MASK | SPI_I2S_CCTL_RXEDGE_MASK); /* Sampling data in the middle of transmission data bits. */ /* DataWidth. */ SPI1->GCTL = ~SPI_I2S_GCTL_DW832_MASK; /* Only the lower 8 bits are valid. */ /* CPOL CPHA. */ SPI1->CCTL = ~(SPI_I2S_CCTL_CPHA_MASK | SPI_I2S_CCTL_CPOL_MASK); /* CPOL = 0, CPHA = 0. */ /* LSB first enable bit. */ SPI1->CCTL = ~SPI_I2S_CCTL_LSBFE_MASK; /* The highest bit of data transmission or reception comes first. */ /* Enbale SPI. */ SPI1->GCTL |= SPI_I2S_GCTL_SPIEN_MASK; }
SPI发送数据 spi_putbyte()
当发送缓冲器未满时,将数据传入发送数据寄存器(SPI_I2S_TXRFG),根据初始化配置,数据低8位有效,通过MOSI引脚串行输出。
void spi_putbyte(uint8_t c) { while (SPI_I2S_CSTAT_TXFULL_MASK SPI1->CSTAT) {} SPI1->TXREG = c; }
SPI接收数据 spi_getbyte()
当接收端缓冲器接收了一个完整字节时,读接收数据寄存器(SPI_I2S_RXREG),返回接收数据。
uint8_t spi_getbyte() { while (0u == (SPI_I2S_CSTAT_RXAVL_MASK SPI1->CSTAT) ) {} return SPI1->RXREG; }
main()函数
main()函数结合上述操作,初始化SPI,定义发送数组spi_tx_buf[16]并赋值,将spi_tx_buf[16]数组中的数值进行发送,定义接收数组spi_rx_buf[16]对数据进行接收。本实验使用杜邦线将MOSI引脚与MISO相连,因此,发送数据与接收数据应相同,将发送数组与接收数组的数值进行比较,定义变量spi_xfer_err_count用于计数发送与接收数值不同的数据个数。若发送与接收数值相同则串口输出"spi loopback xfer done.",若不同则串口输出传输错误与出错个数。实验现象如图6所示。
int main() { enable_clock(); pin_init(); uart_init(); printf("spi_basic example.rn"); spi_init(); for (uint32_t i = 0u; i < 16u; i++) { spi_tx_buf[i] = i; } /* SPI xfer once. */ for (uint32_t i = 0u; i < 16u; i++) { spi_putbyte(spi_tx_buf[i]); spi_rx_buf[i] = spi_getbyte(); } /* validation. */ spi_xfer_err_count = 0u; for (uint32_t i = 0u; i < 16u; i++) { if (spi_rx_buf[i] != spi_tx_buf[i]) { spi_xfer_err_count++; } } if (spi_xfer_err_count == 0u) { printf("spi loopback xfer done.rn"); } else { printf("spi loopback xfer error. spi_xfer_err_count = %urn", (unsigned)spi_xfer_err_count); } while (1) {} }
图6.实验现象
来源: 灵动MM32MCU
全部0条评论
快来发表一下你的评论吧 !