(STM32)从零开始的RT-Thread之旅--SPI驱动ST7735(3)使用DMA

news2024/11/25 16:41:54

上一篇:

(STM32)从零开始的RT-Thread之旅--SPI驱动ST7735(2)

上一篇完成了ST7735驱动的移植,并已经可以通过SPI在屏幕上显示字符了,这一章会把SPI修改为DMA的传输方式。由于RTT对于STM32H7的SPI的DMA传输方式目前支持的并不好,这就让上一章裸机方式驱动屏幕派上了用场。我们可以先把SPI+DMA打通,然后再修改内核驱动框架。

1.不使用驱动框架

把上一章中开启使用RTT驱动框架中的SPI的宏定义注释掉:

然后一定要在配置SPI前使能DMA的时钟:

然后到 stm32h7xx_hal_msp.c 中SPI引脚初始化下面添加相应SPI的DMA配置:

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  if(hspi->Instance==SPI4)
  {
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI4;
    PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_D2PCLK1;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_RCC_SPI4_CLK_ENABLE();

    __HAL_RCC_GPIOE_CLK_ENABLE();
    /**SPI4 GPIO Configuration
    PE12     ------> SPI4_SCK
    PE14     ------> SPI4_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_14;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI4;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET);

    /* SPI4 DMA Init */
    /* SPI4_TX Init */
    hdma_spi4_tx.Instance = DMA1_Stream0;
    hdma_spi4_tx.Init.Request = DMA_REQUEST_SPI4_TX;
    hdma_spi4_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi4_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi4_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi4_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi4_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi4_tx.Init.Mode = DMA_NORMAL;
    hdma_spi4_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    hdma_spi4_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    hdma_spi4_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_spi4_tx.Init.MemBurst = DMA_MBURST_SINGLE;
    hdma_spi4_tx.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_DeInit(&hdma_spi4_tx) != HAL_OK)
    {
      Error_Handler();
    }
    if (HAL_DMA_Init(&hdma_spi4_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hspi,hdmatx,hdma_spi4_tx);

    HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);

    HAL_NVIC_SetPriority(SPI4_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(SPI4_IRQn);
  }
}

这里注意外设和内存对齐:

要和SPI前面配置的数据位数一致,我这里前面是:

还有就是配置结尾要打开相关中断,这里我只使用发送

此时配置基本已经完成,相关配置详细信息可以参考我文末附的几篇教程,都写得很好很详细。

开启中断后,就必须要有中断函数,最最最开始的那一章我提到过,有关CubeMX生成的中断函数的那个.c我们没有加进整个工程里面,所以有些在CubeMX中添加了响应的中断,但是函数需要我们自己再添加进来,这里我添加的函数是:

这里添加的函数需要根据SPI配置的是双工还是单工来选择,所有的回调如下:

这里即使用的双工,也可以只配置一边是DMA,比如我使用的全双工,TX和RX都使用DMA,每次发送和接收只需调用 HAL_SPI_TransmitReceive_DMA ,它既包含发送又包含接收,那触发的中断肯定是 HAL_SPI_TxRxCpltCallback 了。这些由名字很好推断不再赘述。

剩下还需要修改的就是发送的数据所在内存。因为DMA1和2不能访问RTT Studio默认的RAM,默认RAM是从0x2000 0000开始,大小为128K的空间。像我使用的STM32H743VIT6这块芯片,有1M的RAM,但是实际上是分散为几块的,这里修改链接文件:

这里不要直接双击,而要选择打开方式:

这里根据芯片手册,添加剩余的RAM:

在.bss段后添加(ld脚本不熟悉可以看我很早之前写的关于GCC下stm32的ld文件的解析):

这里我们就定义了一个名叫.spi4.txbuf的段,然后把这个段的全部数据放在RAM4的地址范围内。接下来,我们只需要把想要通过SPI的DMA传输的数据保存在这个段内就可以了。在GCC编译器下可以使用__attribute__ 关键字:

uint8_t ram4_spi4_buf[RAM4_SPI4_BUF] __attribute__((section(".spi4.txbuf")));

然后在map文件中查看是否定义成功:

可以看到ram4_spi4_buf这个数组使用了0x3800 0000~0x3800 0400这段地址空间。 这段空间是可以被DMA1访问的。

使用DMA之后最后还需要注意cache设置,这里可能出现数据一致性问题。什么是数据一致性问题?

当MCU运行频率过高时,代码执行效率就会收到RAM频率影响,因为很多时候代码执行需要把寄存器的数据加载到RAM中或者从RAM中读取到寄存器中,但是RAM的频率比MCU低时,MCU必须等待RAM的读写操作,大大降低了MCU的执行效率。这时候出现了一种方案,在RAM和MCU之间多加一块缓存,就是cache,它的读写很快能够跟上MCU,每次MCU需要RAM数据时,会先取一部分数据保存在cache中,如果MCU需要的数据已经在cache中有了,那就直接使用cache中的即可。这就会出现一个问题,我们DMA一定使用的是RAM到外设,而MCU有可能使用的cache中的缓存数据,这两个数据是每时每刻都一样的吗?肯定不是,MCU<-CACHE<-RAM,这cache和RAM之间数据肯定是需要时机和一定时间同步的,说到底,快只是快在MCU和cache已经保存有MCU需要的RAM数据的情况,当MCU需要的数据cache里面没有时,cache一样需要慢慢等待RAM的数据备份到cache里。当MCU需要的数据在cache中存在时,即使DMA改变了RAM中相应的数据,MCU并不知道,它还是会使用cache中的缓存数据,此时就会出现一致性问题。更多详细内容可以参考文末附的文章。

要解决这个问题,我们可以通过配置MPU来关闭某块内存的cache功能。 在 hw_board_init 中可以看到,当有定义使能cache的函数时,会自动调用这些函数:

在这之前我们配置RAM4的MPU:

完整函数如下:

/**
 * This function will initial STM32 board.
 */
void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq)
{
    extern void rt_hw_systick_init(void);
    extern void clk_init(char *clk_source, int source_freq, int target_freq);
    
    MPU_Region_InitTypeDef MPU_InitStruct;
    HAL_MPU_Disable();
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

#ifdef SCB_EnableICache
    /* Enable I-Cache---------------------------------------------------------*/
    SCB_EnableICache();
#endif

#ifdef SCB_EnableDCache
    /* Enable D-Cache---------------------------------------------------------*/
    SCB_EnableDCache();
#endif

    /* HAL_Init() function is called at the beginning of the program */
    HAL_Init();

    /* enable interrupt */
    __set_PRIMASK(0);
    /* System clock initialization */
    clk_init(clock_src, clock_src_freq, clock_target_freq);
    /* disbale interrupt */
    __set_PRIMASK(1);

    rt_hw_systick_init();
    /* Pin driver initialization is open by default */
#ifdef RT_USING_PIN
    extern int rt_hw_pin_init(void);
    rt_hw_pin_init();
#endif

    /* USART driver initialization is open by default */
#ifdef RT_USING_SERIAL
    extern int rt_hw_usart_init(void);
    rt_hw_usart_init();
#endif

}

如果你使用的不是RAM4,可以只简单修改起始地址和大小。

最后回到mspi.c中修改发送接收函数。其中 mspi_send_reg 与 mspi_read_reg 函数我决定不再修改,因为读写reg通常都是一个字节,而使用DMA需要先把我们需要传输的数据copy到 ram4_spi4_buf 这个分配在RAM4的buf中,再使用SPI_DMA传输函数发送,感觉提升并不明显。所以只修改了 mspi_send_data 函数,它通常用来发送需要显示的大量数据,修改如下:

可以看到主要就是先把数据copy到ram4_spi4_buf里,然后用专用于DMA发送的HAL_SPI_Transmit_DMA函数传输。这里DMA传输用的是阻塞等待,发送前把一个全局状态置为busy,等待发送完成后的回调函数里再把状态置idle:

这个函数前面讲需要添加的中断里已经见到过了 ,至于状态定义,如下:

至此所有修改已经完成,正常已经可以测试DMA是否使用正常。 

2.使用驱动框架

首先在board.h中添加使用SPI的TX的DMA的宏定义:

此时编译会发现有好几个错误:

那是因为内核驱动框架根本没有适配STM32H7,我们先修改drv_dma.h中对于DMA相关结构体定义:

然后再修改dma_config.h中对于SPI4的配置:

这里可以根据上面不使用内核驱动框架的时候的 stm32h7xx_hal_msp.c 中的配置修改,反正使用DMA1还是2的哪个流都可以。注意H7没有channel,但有request。 然后配置完成后,就可以把 stm32h7xx_hal_msp.c 中相关配置给注释掉了。这些配置实际应用是在 spi_config.h 中:

有关这些配置的使用都在drv_spi.c中的rt_hw_spi_bus_init函数里,对照上面注释掉的配置,修改如下,具体参数请根据实际需求配置:

注意drv_spi.c中是有SPI4_IRQHandler和SPI4_DMA_TX_IRQHandler中断函数的,所以请注意mspi.c中的中断函数会和它重复定义,请把mspi.c中的中断函数加到USE_RTT_SPI宏定义else代码块内。

添加完这些正常应该可以使用了,但是实际使用会发现代码卡在,返回状态一直是HAL_SPI_STATE_BUSY_TX

调用它的上层函数是spixfer

在这个函数中可以看到当只有发送buf,且使能了TX发送DMA时调用的是:

这个函数中正常发送会把hspi->State 赋值为HAL_SPI_STATE_BUSY_TX:

而这个状态在发送完成后没有被修改回来就会一直处于busy状态,它是在哪里被修改回来呢?

答案是 HAL_SPI_IRQHandler 函数 。而RTT内核默认是没有使用SPI的中断传输方式的,这可以从 spixfer 这个函数中调用的底层发送接收函数得知,所以当开启DMA后,又没有开启SPI中断就会出现这种情况。可以在中断函数内打上断点测试:

修改方式也很简单,假如没有使用RTT,可以直接在DMA使能中断的地方直接添加SPI中断配置与使能,这里我介绍一下在使用RTT下如何修改。在drv_spi.h中修改结构体:

在spi_config.h中添加:

在 drv_spi.c 中初始化函数 stm32_spi_init 的最后使能DMA中断处添加:

至此框架修改完毕,最后在上一章的基础上修改我们的发送接收函数,因为我们调用的是驱动框架的接口,不修改接口的情况下没办法像不使用框架那样,把普通SPI传输和SPI-DMA传输混合使用,这里只能都使用DMA传输,修改如下:

其他几个函数附在文章最后,仅供参考。 

mspi.c 

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-11-14     cx       the first version
 */
#include<string.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <stm32h7xx.h>
#include <drv_spi.h>
#include <mspi.h>
#include "st7735_reg.h"
#include "st7735.h"
enum
{
    idle,
    busy,
    error
};
volatile uint32_t spi4_dma_status = idle;

uint8_t ram4_spi4_buf[RAM4_SPI4_BUF] __attribute__((section(".spi4.txbuf")));


#define USE_RTT_SPI

SPI_HandleTypeDef *spi_handle;

#ifdef USE_RTT_SPI

static struct rt_spi_device *spi_lcd;

#define LCD_RD_HIGH rt_pin_write(SPI_RD_PIN_NUM, PIN_HIGH)
#define LCD_RD_LOW  rt_pin_write(SPI_RD_PIN_NUM, PIN_LOW)
#else
extern SPI_HandleTypeDef hspi4;
#define SPI_Drv (&hspi4)
#define LCD_CS_HIGH HAL_GPIO_WritePin(GPIOE,GPIO_PIN_11,GPIO_PIN_SET)
#define LCD_CS_LOW  HAL_GPIO_WritePin(GPIOE,GPIO_PIN_11,GPIO_PIN_RESET)

#define LCD_RD_HIGH HAL_GPIO_WritePin(GPIOE,GPIO_PIN_13,GPIO_PIN_SET)
#define LCD_RD_LOW  HAL_GPIO_WritePin(GPIOE,GPIO_PIN_13,GPIO_PIN_RESET)
#endif

#ifdef USE_RTT_SPI
int32_t mspi_send_reg(uint8_t reg,uint8_t *data,uint32_t len)
{
    struct rt_spi_message msg;
    uint32_t remsg = RT_NULL;
    ram4_spi4_buf[0] = reg;
    memcpy(&ram4_spi4_buf[1],data,len);
    msg.send_buf = &ram4_spi4_buf[0];
    msg.recv_buf = RT_NULL;
    msg.length = 1;
    msg.cs_take = 1;
    msg.cs_release = 0;
    msg.next = RT_NULL;
    LCD_RD_LOW;
    remsg = (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
    LCD_RD_HIGH;
    if(len > 0)
    {
        msg.send_buf = &ram4_spi4_buf[1];
        msg.recv_buf = RT_NULL;
        msg.length = len;
        msg.cs_take = 0;
        msg.cs_release = 1;
        msg.next = RT_NULL;
        remsg += (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
    }
    if(remsg!=RT_NULL)
        return -1;
    else
        return 0;
}
int32_t mspi_read_reg(uint8_t reg,uint8_t *data)
{
    struct rt_spi_message msg;
    uint32_t remsg = RT_NULL;
    ram4_spi4_buf[0] = reg;
    msg.send_buf = &ram4_spi4_buf[0];
    msg.recv_buf = RT_NULL;
    msg.length = 1;
    msg.cs_take = 1;
    msg.cs_release = 0;
    msg.next = RT_NULL;
    LCD_RD_LOW;
    remsg = (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
    LCD_RD_HIGH;
    if(remsg == 0)
    {
        msg.send_buf = RT_NULL;
        msg.recv_buf = data;
        msg.length = 1;
        msg.cs_take = 0;
        msg.cs_release = 1;
        msg.next = RT_NULL;
        remsg += (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
    }
    if(remsg!=RT_NULL)
        return -1;
    else
        return 0;
}
int32_t mspi_send_data(uint8_t *data,uint32_t len)
{
    struct rt_spi_message msg;
    memcpy(ram4_spi4_buf,data,len);
    msg.send_buf = ram4_spi4_buf;
    msg.recv_buf = RT_NULL;
    msg.length = len;
    msg.cs_take = 1;
    msg.cs_release = 1;
    msg.next = RT_NULL;
    return (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
}
int32_t mspi_read_data(uint8_t *data,uint32_t len)
{
    struct rt_spi_message msg;
    msg.send_buf = RT_NULL;
    msg.recv_buf = data;
    msg.length = len;
    msg.cs_take = 1;
    msg.cs_release = 1;
    msg.next = RT_NULL;
    return (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);
}

#else
int32_t mspi_send_reg(uint8_t reg,uint8_t *data,uint32_t len)
{
    int32_t result;
    LCD_CS_LOW;
    LCD_RD_LOW;
    result = HAL_SPI_Transmit(SPI_Drv,&reg,1,100);
    LCD_RD_HIGH;
    if(len > 0)
        result += HAL_SPI_Transmit(SPI_Drv,data,len,500);
    LCD_CS_HIGH;
    if(result>0){
        result = -1;}
    else{
        result = 0;}
    return result;
}

int32_t mspi_read_reg(uint8_t reg,uint8_t *data)
{
    int32_t result;
    LCD_CS_LOW;
    LCD_RD_LOW;

    result = HAL_SPI_Transmit(SPI_Drv,&reg,1,100);
    LCD_RD_HIGH;
    result += HAL_SPI_Receive(SPI_Drv,data,1,500);
    LCD_CS_HIGH;
    if(result>0){
        result = -1;}
    else{
        result = 0;}
    return result;
}

int32_t mspi_send_data(uint8_t *data,uint32_t len)
{
    int32_t result;
    memcpy(ram4_spi4_buf,data,len);
    LCD_CS_LOW;
//    result =HAL_SPI_Transmit(SPI_Drv,data,len,100);
    spi4_dma_status = busy;
    if(HAL_SPI_Transmit_DMA(SPI_Drv,ram4_spi4_buf,len) != HAL_OK)
    {
        rt_kprintf("SPI4 DMA trans err\n");
        return -1;
    }
    else
    {
        while(spi4_dma_status==busy);
        result = 0;
    }
    LCD_CS_HIGH;
    if(result>0)
    {
        result = -1;
    }
    else
    {
        result = 0;
    }
    return result;
}

int32_t mspi_read_data(uint8_t *data,uint32_t len)
{
    int32_t result;
    LCD_CS_LOW;
    result = HAL_SPI_Receive(SPI_Drv,data,len,500);
    LCD_CS_HIGH;
    if(result>0)
    {
        result = -1;
    }
    else
    {
        result = 0;
    }
    return result;
}

extern DMA_HandleTypeDef hdma_spi4_tx;

void DMA1_Stream0_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_spi4_tx);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    spi4_dma_status = idle;
}
void SPI4_IRQHandler(void)
{
  HAL_SPI_IRQHandler(&hspi4);
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
    spi4_dma_status = error;
}

#endif
int32_t mspi_get_tick(void)
{
    return HAL_GetTick();
}
void mspi_rw_gpio_init(void)
{
#ifdef USE_RTT_SPI
    rt_pin_mode(SPI_RD_PIN_NUM, PIN_MODE_OUTPUT);
    rt_pin_write(SPI_RD_PIN_NUM, PIN_HIGH);
#else
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOE_CLK_ENABLE();
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11|GPIO_PIN_13, GPIO_PIN_SET);
    GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
#endif
}
void mspi_init(void)
{
    mspi_rw_gpio_init();
#ifdef USE_RTT_SPI
    struct rt_spi_configuration cfg;
    rt_hw_spi_device_attach("spi4", "spi40", GPIOE, GPIO_PIN_11);
    spi_lcd = (struct rt_spi_device *)rt_device_find("spi40");
    if(!spi_lcd)
    {
        rt_kprintf("spi40 can't find\n");
    }
    else
    {
        spi_lcd->bus->owner = spi_lcd;
        cfg.data_width = 8;
        cfg.mode = RT_SPI_MASTER | RT_SPI_3WIRE | RT_SPI_MODE_0 | RT_SPI_MSB;
        cfg.max_hz = 12.5 * 1000 * 1000;
        rt_spi_configure(spi_lcd, &cfg);
    }
    //也可以初始化使用框架提供的函数,发送接收使用HAL库函数,操作对象就是下面的 spi_handle
//    struct stm32_spi *spi_drv =  rt_container_of(spi_lcd->bus, struct stm32_spi, spi_bus);
//    spi_handle = &spi_drv->handle;
#else
    __HAL_RCC_DMA1_CLK_ENABLE();
    hspi4.Instance = SPI4;
    hspi4.Init.Mode = SPI_MODE_MASTER;
    hspi4.Init.Direction = SPI_DIRECTION_1LINE;
    hspi4.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi4.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi4.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi4.Init.NSS = SPI_NSS_SOFT;
    hspi4.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
    hspi4.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi4.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi4.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi4.Init.CRCPolynomial = 0x0;
    hspi4.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
    hspi4.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
    hspi4.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
    hspi4.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
    hspi4.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
    hspi4.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
    hspi4.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
    hspi4.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
    hspi4.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
    hspi4.Init.IOSwap = SPI_IO_SWAP_DISABLE;
    if (HAL_SPI_Init(&hspi4) != HAL_OK)
    {
      rt_kprintf("ERROR\n");
    }
#endif
}

参考文章:

【STM32H7教程】第72章 STM32H7的SPI总线基础知识和HAL库API

【STM32H7教程】第94章 STM32H7的SPI总线应用之双机通信(DMA方式)

 STM32 H7 配置SPI&DMA小结

STM32H7---高速缓存Cache(一) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/27850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vuex3使用教程(待续)

Vuex定义 以下是Vue官网对于Vuex的定义&#xff1a; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 从官方定义上看&#xff1a; Vuex提供了一个全部组件…

Java注释:单行、多行和文档注释

注释是对程序语言的说明&#xff0c;有助于开发者和用户之间的交流&#xff0c;方便理解程序。注释不是编程语句&#xff0c;因此被编译器忽略。 Java入门基础视频教程&#xff0c;java零基础自学就选黑马程序员Java入门教程&#xff08;含Java项目和Java真题&#xff09; Ja…

【Django】Django4.1.2使用xadmin避坑指南(二)

上一篇【Django】Django4.1.2使用xadmin避坑指南调完后&#xff0c;还是继续有问题&#xff0c;没事&#xff0c;咱们继续&#xff0c;必须啃下硬骨头~ 文章目录环境问题一&#xff1a;if not ContentType._meta.installed:这一句报错&#xff1a;AttributeError: Options obje…

《深度学习进阶 自然语言处理》第八章:Attention介绍

文章目录8.1 Attention结构8.1.1 seq2seq存在的问题8.1.2 编码器的改进8.1.3 解码器的改进8.2 Attention的应用8.3 总结之前文章链接&#xff1a; 开篇介绍&#xff1a;《深度学习进阶 自然语言处理》书籍介绍 第一章&#xff1a;《深度学习进阶 自然语言处理》第一章&#xf…

SSH连接WSL2踩坑记录与增加端口转换规则,实现外网与WSL2的连接

SSH连接WSL2踩坑记录 文章目录SSH连接WSL2踩坑记录1. 在WSL里的操作2. ssh连接3. 可能出现的错误4. 再配置端口转发到WSL1. 在WSL里的操作 1.1 重装openssh-server sudo remove openssh-server # 如果已经安装了&#xff0c;建设先卸载 sudo apt install openssh-server…

Ansys Lumerical | 行波 Mach-Zehnder 调制器仿真分析

前言 本示例描述了行波 Mach-Zehnder 调制器的完整多物理场&#xff08;电气、光学、射频&#xff09;仿真&#xff0c;最后在INTERCONNECT中进行了紧凑模型电路仿真。计算了相对相移、光学传输、传输线带宽和眼图等关键结果。 综述 此示例中5毫米长的Si波导由5毫米长的Al共面…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.24 SpringBoot 整合 RabbitMQ(topic 模式)

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.24 SpringBoot 整合 RabbitMQ(topic 模式)5.24.1 …

FL Studio2023水果编曲软件最新版安装教程

FL Studio中文版是知名的音乐制作软件&#xff0c;让你的计算机就像是全功能的录音室&#xff0c;软件包含13种虚拟音源&#xff0c;可同时录制64轨音频轨&#xff0c;FL Studio中文版拥有的漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制&a…

智能化油田建设规划

一、数字化油田-技术现状 数字化油田实现了设备的远程生产过程监控&#xff0c;使井场实现无人值守。所以目前的设备运行维护管理系统只能实现数据统计管理&#xff0c;并不能实现设备状态监控及远程维护及故障诊断。 1、数字化油田— 存在的问题 缺少设备状态在线监测系统&a…

第三章. 业务功能开发--用户登录安全退出

第三章. 业务功能开发--用户登录安全退出 1. 用户登录 需求&#xff1a; 用户在登录页面,输入用户名和密码,点击"登录"按钮或者回车,完成用户登录的功能.*用户名和密码不能为空*用户名或者密码错误,用户已过期,用户状态被锁定,ip受限 都不能登录成功*登录成功之后,所…

Android Jetpack之Lifecycle的使用及源码分析

Lifecycle生命周期感知型组件可执行操作来响应另一个组件&#xff08;如 Activity 和 Fragment&#xff09;的生命周期状态的变化。这些组件有助于您编写出更有条理且往往更精简的代码&#xff0c;此类代码更易于维护。 尤其是在Activity和Fragment在已经默认支持LifeCycle的情…

【第五部分 | JS WebAPI】3:DOM 节点操作

目录 | 节点操作 1-1 概述 2-1 获取父节点 3-1 获取子节点&#xff08;获取所有子对象 不推荐&#xff09; 3-2 获取子节点&#xff08;获取所有子【元素节点】&#xff09; 3-3 获取首尾子节点 4-1 获取兄弟节点 5-1 动态创建、添加节点 5-2 案例&#xff1a;评论区 …

性能测试_JMeter_connection timed out :connect

jmeter报错:failed:connection timed out :connect/java.net.BindException: Address already in use: connect java.net.BindException: Address already in use: connectat java.net.DualStackPlainSocketImpl.connect0(Native Method)at java.net.DualStackPlainSocketImpl…

Linux系统上安装软件

安装jdk&#xff0c;安装tomcat&#xff0c;安装Mysql 四种安装方式&#xff1a; 安装jdk 1.去这个网站上下载linux版本的jdk Java Archive Downloads - Java SE 8 2.在虚拟机中的服务器终端中输入ifconfig&#xff08;注意不是ipconfig&#xff0c;而是ifconfig…

智慧机场解决方案-最新全套文件

智慧机场解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 智慧机场全套最新解决方案合集一、建设背景 中国处在机场持续大规模建设过程中&#xff0c;政府也有意愿建设机场作为城市名片&#xff0c;经济持续增长会带来机场的持续建设&#xff1b;我国机…

螺旋模型的优点与缺点

螺旋模型&#xff1a; 特点&#xff1a; 螺旋模型在“瀑布模型”的每一个开发阶段前引入一个非常严格的风险识别、风险分析和风险控制&#xff0c;它把软件项目分解成一个个小项目。每个小项目都标识一个或多个主要风险&#xff0c;直到所有的主要风险因素都被确定 螺旋模型强…

Copilot:AI自动写代码,人工智能究竟还能取代什么?

Copilot&#xff1a;AI自动写代码&#xff0c;人工智能究竟还能取代什么&#xff1f; 前言 在AI绘画掀起一阵热潮之后&#xff0c;AI写代码又逐渐进入了我们的视野&#xff0c;似乎这一步我们还没想到就迅速到来了&#xff0c;难道说AI在取代画家之后&#xff0c;还要取代程序…

引擎入门 | Unity UI简介–第1部分(7)

本期我们继续为大家进行Unity UI简介&#xff08;第一部分&#xff09;的后续教程 本篇内容 14.放置标题图像 15.添加开始按钮 16.定位按钮 文章末尾可免费获取教程源代码 本篇Unity UI简介&#xff08;第一部分&#xff09;篇幅较长&#xff0c;分为十篇&#xff0c;本篇…

sqli-labs/Less-48

欢迎界面还是以sort为注入参数 接下来进行注入类型的判断 首先输入一下内容 sortrand() 多尝试几次 发现界面会发生变化 所以这一关属于数字型注入 然后我们选择使用报错注入 尝试输入一下内容 sortupdatexml(1,if(11,concat(0x7e,database(),0x7e),1),1)-- 回显如下 呦…

网络层 408真题 大题详解

1、【2009】 第一问有两种分配可能性&#xff0c;要想到位 记住&#x1f6a9; 路由器到互联网的路由相当于默认路由0/0 即目的地址0.0.0.0 子网掩码0.0.0.0 2、【2015】 注意&#x1f6a9;DHCP服务器不能转发信息 要观察到本图网络拓扑是同一个网络&#xff0c;在同一个网络中…