- /************************************************
- 函数名称 : SPI_GPIO_Configuration
- 功 能 : SPI GPIO初始化
- 参 数 : 无
- 返 回 值 : 无
- 作 者 : Mico
- *************************************************/
- void SPI_GPIO_Configuration(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- SPI_InitTypeDef SPI_InitStructure;
-
- /* 使能AHB时钟 */
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
- /* 使能APB2时钟 */
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
- /*定义 SPI复用引脚 */
- GPIO_InitStructure.GPIO_Pin = PIN_SPI_SCK | PIN_SPI_MISO | PIN_SPI_MOSI;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //高速输出
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
- GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);
-
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_0);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_0);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_0);
-
- /* 片选CS */
- GPIO_InitStructure.GPIO_Pin = PIN_SPI_CS;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //高速输出
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
- GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);
- GPIO_ResetBits(GPIOB, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5);//拉低片选
- /* SPI 初始化定义 */
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主 SPI
- SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收 8 位帧结构
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制 NSS 信号
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率预分频值为8
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
- SPI_InitStructure.SPI_CRCPolynomial = 8; //定义了用于 CRC值计算的多项式
- SPI_Init(SPI1, &SPI_InitStructure);
- SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF); //区别于STM32F103
- SPI_Cmd(SPI1, ENABLE);
- }
因为SPI是收发一体,所以在需要发送24位数据时SPI使用该函数进行三次收发,代码显得十分臃肿。
- /************************************************
- 函数名称 : SPI_WriteReadByte
- 功 能 : 对SPI写读字节数据
- 参 数 : 无
- 返 回 值 : 32位角度值
- 作 者 : Mico
- *************************************************/
- /***************************************************
- DR寄存器是16位的,如果你直接SPI1->DR = 0x9F ;
- 这样的操作是不正确的,你的数据会变成0x009F之后赋值给DR寄存器,
- 也就是操作了16位,所以STM32会输出16个时钟脉冲
- 我们先找到DR寄存器的地址,再用一个八位的指针指向这个地址,
- 现在指向的是DR寄存器的开头,那么指针+1,指针指向了DR寄存器的低八位
- 这时候给指针指向的地址赋值0x85,那么这个字节就会放入DR低八位的空间内,
- 而不是操作整个16位DR寄存器
- ***************************************************/
- uint32_t SPI_WriteRead(void)
- {
- uint16_t num1,num2,num3;
- uint32_t AngelData;
- GPIO_ResetBits(GPIOA, GPIO_Pin_15);//拉低片选
- *((uint8_t*)&(SPI1->DR) + 1 ) = 0x50;
- num1 = SPI1->DR;
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
-
- *((uint8_t*)&(SPI1->DR) + 1 ) = 0x35;
- num2 = SPI1->DR;
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
-
- *((uint8_t*)&(SPI1->DR) + 1 ) = 0x0F;
- num3 = SPI1->DR;
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
- GPIO_SetBits(GPIOA, GPIO_Pin_15);//拉高片选
- AngelData = ((num2&0xFF)<<16 |(num3&0xFF)<<8 | (num1&0xFF));
- return AngelData ;
- }
新塘M031 SPI代码:
因为新塘时钟的初始化是写在一个函数中的,我沿用了这个习惯,初始化系统以及各个外设时钟,这个必不可少。
- void SYS_Init(void)
- {
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init System Clock */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Unlock protected registers */
- SYS_UnlockReg();
- /* Enable HIRC clock (Internal RC 48MHz) */
- CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);
- /* Wait for HIRC clock ready */
- CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);
- /* Select HCLK clock source as HIRC and HCLK source divider as 1 */
- CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));
- /* Set both PCLK0 and PCLK1 as HCLK */
- CLK->PCLKDIV = CLK_PCLKDIV_APB0DIV_DIV1 | CLK_PCLKDIV_APB1DIV_DIV1;
- /* Select IP clock source */
- /* Select UART0 clock source is HIRC */
- CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));
- /* Select UART1 clock source is HIRC */
- CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));
- /* Select PCLK1 as the clock source of SPI0 */
- CLK_SetModuleClock(SPI0_MODULE, CLK_CLKSEL2_SPI0SEL_PCLK1, MODULE_NoMsk);
-
- /* Enable UART0 peripheral clock */
- CLK_EnableModuleClock(UART0_MODULE);
- /* Enable UART1 peripheral clock */
- CLK_EnableModuleClock(UART1_MODULE);
- /* Enable PDMA module clock */
- CLK_EnableModuleClock(PDMA_MODULE);
- /* Enable SPI0 peripheral clock */
- CLK_EnableModuleClock(SPI0_MODULE);
-
- /* Update System Core Clock */
- /* User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. */
- SystemCoreClockUpdate();
- /*---------------------------------------------------------------------------------------------------------*/
- /* Init I/O Multi-function */
- /*---------------------------------------------------------------------------------------------------------*/
- /* Set PB multi-function pins for UART0 RXD=PB.12 and TXD=PB.13 */
- SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk)) | \
- (SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD);
- /* Set PB multi-function pins for UART1 RXD(PB.2) and TXD(PB.3) */
- SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk)) | \
- (SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD);
- /* Setup SPI0 multi-function pins */
- /* PA.3 is SPI0_SS, PA.2 is SPI0_CLK,
- PA.1 is SPI0_MISO, PA.0 is SPI0_MOSI*/
- SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA3MFP_Msk |
- SYS_GPA_MFPL_PA2MFP_Msk |
- SYS_GPA_MFPL_PA1MFP_Msk |
- SYS_GPA_MFPL_PA0MFP_Msk)) |
- (SYS_GPA_MFPL_PA3MFP_SPI0_SS |
- SYS_GPA_MFPL_PA2MFP_SPI0_CLK |
- SYS_GPA_MFPL_PA1MFP_SPI0_MISO |
- SYS_GPA_MFPL_PA0MFP_SPI0_MOSI);
-
- /* Lock protected registers */
- SYS_LockReg();
- }
超级简洁的SPI初始化:
- void SPI_Init(void)
- {
- SPI_Open(SPI0, SPI_MASTER, SPI_MODE_1, 24, 8000000);
- SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);
- }
此处将SPI0设置为主模式,24位字长和8M的速度。
在主函数中测试:
- while(1)
- {
- SPI_SET_DATA_WIDTH(SPI0,24);
-
- /* Write to TX register */
- SPI_WRITE_TX(SPI0, 0x50650F);
- /* Check SPI0 busy status */
- while(SPI_IS_BUSY(SPI0));
- SPI_SET_DATA_WIDTH(SPI0,32);
- /* Write to TX register */
- SPI_WRITE_TX(SPI0, 0x50650F55);
- /* Check SPI0 busy status */
- while(SPI_IS_BUSY(SPI0));
- }
首先将发送的字长设定为24位,然后输出,完成后将输出字长改为32,再次输出。在这里我遇到了麻烦事,输出的数据根本对不上,在使用逻辑分析仪查看数据如下:
无论是数据还是时钟均对不上,然后将时钟降低到2M,输出如下:
可以很明显的看到数据完全正确,时钟连续性很好,我怀疑是SPI的时钟线配置有问题,可能是主频分频过来的的时钟太低,达不到8M,但是在时钟配置中检查不到任何问题,于是在这里转圈圈,折腾了好久,后来想到去年在调试STM32F031的SPI时也遇到莫名其妙的问题,当时发现是逻辑分析仪的最大速度只有2M,高于2M显示就不准确。
果然,在2M和8M的频率下接上示波器,观察两个波形其实是相同的
2M
8M
问题分析:逻辑分析仪只能抓取分析波形好的逻辑,在高速情况下,加上逻辑分析仪的线较长,可能导致波形变差,导致逻辑分析仪无法抓取全部波形。另外一个就是速度太快,逻辑分析仪的响应速度达不到那么高。
同一个坑,踩了两次,真的有够烦人的,以后一定要记住逻辑分析仪遇到问题考虑一下示波器?
关于SPI的DMA
无论是STM32还是新塘,在仅仅操作SPI的时候可以在MCU允许的情况下进行自由字节数的收发,但是我们很多时候都需要使用DMA来搬运数据,以此减小CPU的工作量。这里我们看一下DMA的描述:
DMA的单次传输字节数并不是很自由,只有8位,16位和32位三种模式。比如我发送24位数据,DMA数据宽度得设置为32位,即使我的SPI定义为24位的宽度,实际DMA还是会产生32个时钟信号,最高位会填充0。所以当使用DMA通信时,还是得将SPI设置为8位宽度,DMA设置为8位,将24位数据分为3个8位送出。