Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
SPI
STM32
标准库
rt-thread基于stm32标准库的SPI驱动
发布于 2020-08-01 00:05:12 浏览:2363
订阅该版
[tocm] 发现rt-thread在某个版本更新中,stm32 BSP下的库函数从标准库切换到了HAL库,HAL库应该是stm32日后发展的主流,但是个人感觉标准库更简洁,易于理解,因此在旧版的RTT上改写了一版SPI的驱动,便于加深对SPI的理解。 关于SPI的协议有大量的文章描述,因此不再赘述。 # 一、内核中的SPI device SPI包含以下几个结构体: ``` struct rt_spi_device { struct rt_device parent; struct rt_spi_bus *bus; struct rt_spi_configuration config; void *user_data; }; struct rt_spi_bus { struct rt_device parent; rt_uint8_t mode; const struct rt_spi_ops *ops; struct rt_mutex lock; struct rt_spi_device *owner; }; struct rt_spi_ops { rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration); rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message); }; ``` 因此,我们在驱动中需要实现的部分,就是将stm32的SPI总线注册到内核中,并且实现底层的ops,以及SPI设备的挂载。 # 二、驱动中的结构体 ``` struct stm32_hw_spi_cs { GPIO_TypeDef* GPIOx; uint16_t GPIO_Pin; }; struct stm32_spi { SPI_TypeDef *instance; char *bus_name; SPI_InitTypeDef init; struct rt_spi_bus spi_bus; }; ``` stm32_hw_spi_cs是片选引脚的结构体,用于设备挂载到总线。 通过查询手册,SPI1引脚使用如下: NSS----PA4 SCK----PA5 MISO----PA6 MOSI----PA7 # 三、驱动中的函数 首先是总线的挂载,此处仅以SPI1为例。 ``` static int stm32_hw_spi_bus_init(void) { struct stm32_spi *spi_bus; RCC_Configuration(); GPIO_Configuration(); spi_bus = &spi1; spi_bus->instance = SPI1; spi_bus->bus_name = "spi1"; spi_bus->spi_bus.parent.user_data = &spi1; rt_spi_bus_register(&spi_bus->spi_bus, spi_bus->bus_name, &stm_spi_ops); LOG_D("%s bus init done", spi_bus->bus_name); return 0; } INIT_BOARD_EXPORT(stm32_hw_spi_bus_init); ``` 逐个来看以下这一函数做了哪些事情。首先是RCC_Configuration。 ``` static void RCC_Configuration(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); } ``` RCC_Configuration中使能了SPI1和GPIOA的时钟。 然后是GPIO_Configuration。 ``` static void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; /* Connect alternate function */ GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); GPIO_Init(GPIOA, &GPIO_InitStructure); } ``` GPIO_Configuration中配置了SPI各个引脚的功能。 之后开始配置spi1,spi1由之前的stm32_spi结构体定义,然后调用rt_spi_bus_register,将spi1注册到内核。 注册时需要传入ops参数,ops是内核中SPI device的操作,因此在驱动中定义ops如下: ``` static const struct rt_spi_ops stm_spi_ops = { spi_configure, spixfer, }; ``` configure函数实现如下,设置指定的配置。 ``` static rt_err_t spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration) { RT_ASSERT(device != RT_NULL); RT_ASSERT(configuration != RT_NULL); struct stm32_spi *spi_drv = rt_container_of(device->bus, struct stm32_spi, spi_bus); SPI_InitTypeDef *spi_init = &spi_drv->init; if (cfg->mode & RT_SPI_SLAVE) { spi_init->SPI_Mode = SPI_Mode_Slave; } else { spi_init->SPI_Mode = SPI_Mode_Master; } if (cfg->mode & RT_SPI_3WIRE) { spi_init->SPI_Direction = SPI_Direction_1Line_Rx; } else { spi_init->SPI_Direction = SPI_Direction_2Lines_FullDuplex; } if (cfg->data_width == 8) { spi_init->SPI_DataSize = SPI_DataSize_8b; } else if (cfg->data_width == 16) { spi_init->SPI_DataSize = SPI_DataSize_16b; } else { return RT_EIO; } if (cfg->mode & RT_SPI_CPHA) { spi_init->SPI_CPHA = SPI_CPHA_2Edge; } else { spi_init->SPI_CPHA = SPI_CPHA_1Edge; } if (cfg->mode & RT_SPI_CPOL) { spi_init->SPI_CPOL = SPI_CPOL_High; } else { spi_init->SPI_CPOL = SPI_CPOL_Low; } if (cfg->mode & RT_SPI_NO_CS) { spi_init->SPI_NSS = SPI_NSS_Soft; } else { spi_init->SPI_NSS = SPI_NSS_Soft; } uint32_t SPI_APB_CLOCK; #if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0) SPI_APB_CLOCK = SystemCoreClock>>APBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE1)>> 10U] #else SPI_APB_CLOCK = SystemCoreClock>>APBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE2)>> 13U]; #endif if (cfg->max_hz >= SPI_APB_CLOCK / 2) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; } else if (cfg->max_hz >= SPI_APB_CLOCK / 4) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; } else if (cfg->max_hz >= SPI_APB_CLOCK / 8) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; } else if (cfg->max_hz >= SPI_APB_CLOCK / 16) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; } else if (cfg->max_hz >= SPI_APB_CLOCK / 32) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; } else if (cfg->max_hz >= SPI_APB_CLOCK / 64) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; } else if (cfg->max_hz >= SPI_APB_CLOCK / 128) { spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; } else { /* min prescaler 256 */ spi_init->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; } RCC_ClocksTypeDef RCC_ClocksStatus; RCC_GetClocksFreq(&RCC_ClocksStatus); LOG_D("sys freq: %d, pclk2 freq: %d, SPI limiting freq: %d, SPI_BaudRatePrescaler: %d", RCC_ClocksStatus.SYSCLK_Frequency, SPI_APB_CLOCK, cfg->max_hz, spi_init->SPI_BaudRatePrescaler); if (cfg->mode & RT_SPI_MSB) { spi_init->SPI_FirstBit = SPI_FirstBit_MSB; } else { spi_init->SPI_FirstBit = SPI_FirstBit_LSB; } spi_init->SPI_CRCPolynomial = 7; #if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32G0) spi_init->NSSPMode = SPI_NSS_PULSE_DISABLE; #endif SPI_Init(spi_drv->instance, spi_init); SPI_Cmd(spi_drv->instance, ENABLE); #if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F0) \ || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G0) SET_BIT(spi_init->instance->CR2, SPI_RXFIFO_THRESHOLD_HF); #endif spi_drv->instance->CR1 |= SPI_CR1_SPE; LOG_D("%s init done", spi_drv->bus_name); return RT_EOK; } ``` xfer函数如下: ``` static rt_uint32_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message) { int state; rt_size_t message_length, already_send_length; rt_uint16_t send_length; rt_uint8_t *recv_buf; const rt_uint8_t *send_buf; RT_ASSERT(device != RT_NULL); RT_ASSERT(device->bus != RT_NULL); RT_ASSERT(device->bus->parent.user_data != RT_NULL); RT_ASSERT(message != RT_NULL); struct stm32_spi *spi_drv = rt_container_of(device->bus, struct stm32_spi, spi_bus); SPI_TypeDef *spi_instance = spi_drv->instance; SPI_InitTypeDef *spi_init = &spi_drv->init; struct stm32_hw_spi_cs *cs = device->parent.user_data; if (message->cs_take) { GPIO_WriteBit(cs->GPIOx, cs->GPIO_Pin, Bit_RESET); } LOG_D("%s transfer prepare and start", spi_drv->bus_name); LOG_D("%s sendbuf: %X, recvbuf: %X, length: %d", spi_drv->bus_name, (uint32_t)message->send_buf, (uint32_t)message->recv_buf, message->length); message_length = message->length; recv_buf = message->recv_buf; send_buf = message->send_buf; while (message_length) { if (message_length > 65535) { send_length = 65535; message_length = message_length - 65535; } else { send_length = message_length; message_length = 0; } /* calculate the start address */ already_send_length = message->length - send_length - message_length; send_buf = (rt_uint8_t *)message->send_buf + already_send_length; recv_buf = (rt_uint8_t *)message->recv_buf + already_send_length; if (message->send_buf && message->recv_buf) { state = spi_transmitreceive(*spi_init, spi_instance, (uint8_t *)send_buf, (uint8_t *)recv_buf, send_length, 1000); } else if (message->send_buf) { state = spi_transmit(*spi_init, spi_instance, (uint8_t *)send_buf, send_length, 1000); } else { memset((uint8_t *)recv_buf, 0xff, send_length); state = spi_receive(*spi_init, spi_instance, (uint8_t *)recv_buf, send_length, 1000); } if (state != 0) { LOG_I("spi transfer error : %d", state); message->length = 0; } else { LOG_D("%s transfer done", spi_drv->bus_name); } } if (message->cs_release) { GPIO_WriteBit(cs->GPIOx, cs->GPIO_Pin, Bit_SET); } return message->length; } ``` 函数根据内核调用时的不同传输情况,需要实现三个传输函数,如下: ``` int spi_transmit(SPI_InitTypeDef spi_init, SPI_TypeDef *spi_instance, uint8_t *pData, uint16_t Size, uint32_t Timeout) { uint32_t tickstart = 0U; int errorcode = 0; __IO uint16_t TxXferCount; __IO uint32_t SPITimeout; /* Init tickstart for timeout management*/ tickstart = rt_tick_get() * 1000 / RT_TICK_PER_SECOND; if((pData == NULL ) || (Size == 0)) { errorcode = 1; goto error; } TxXferCount = Size; /* Configure communication direction : 1Line */ if(spi_init.SPI_Direction == SPI_Direction_1Line_Rx) { spi_instance->CR1 |= SPI_CR1_BIDIOE; } /* Check if the SPI is already enabled */ if((spi_instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { /* Enable SPI peripheral */ spi_instance->CR1 |= SPI_CR1_SPE; } /* Transmit data in 16 Bit mode */ if(spi_init.SPI_DataSize == SPI_DataSize_16b) { if((spi_init.SPI_Mode == SPI_Mode_Slave) || (TxXferCount == 0x01)) { spi_instance->DR = *((uint16_t *)pData); pData += sizeof(uint16_t); TxXferCount--; } /* Transmit data in 16 Bit mode */ while (TxXferCount > 0U) { SPITimeout = 0x1000; /* Wait until TXE flag is set to send data */ while (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_TXE) == RESET) { if((SPITimeout--) == 0) { rt_kprintf("spi超时\n"); errorcode = 3; goto error; } } /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */ spi_instance->DR = *((uint16_t *)pData); pData += sizeof(uint16_t); TxXferCount--; } } /* Transmit data in 8 Bit mode */ else { if((spi_init.SPI_Mode == SPI_Mode_Slave) || (TxXferCount == 0x01U)) { *((__IO uint8_t*)&spi_instance->DR) = (*pData); pData += sizeof(uint8_t); TxXferCount--; } while(TxXferCount > 0) { SPITimeout = 0x1000; /* 等待发送缓冲区为空,TXE事件 */ while (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_TXE) == RESET) { if((SPITimeout--) == 0) { rt_kprintf("spi超时\n"); errorcode = 3; goto error; } } /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */ *((__IO uint8_t*)&spi_instance->DR) = (*pData); pData += sizeof(uint8_t); TxXferCount--; } } /* Wait until TXE flag */ if(spi_wait_until_timeout(spi_init, spi_instance, SPI_FLAG_TXE, SET, Timeout, tickstart) != 0) { errorcode = 3; goto error; } /* Check Busy flag */ if(spi_wait_until_timeout(spi_init, spi_instance, SPI_FLAG_TXE, SET, Timeout, tickstart) != 0) { errorcode = 1; goto error; } error: return errorcode; } int spi_transmitreceive(SPI_InitTypeDef spi_init, SPI_TypeDef *spi_instance, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) { uint32_t tmp = 0U; uint32_t tickstart = 0U; /* Variable used to alternate Rx and Tx during transfer */ uint32_t txallowed = 1U; int errorcode = 0; __IO uint16_t TxXferCount; __IO uint16_t RxXferCount; /* Init tickstart for timeout management*/ tickstart = rt_tick_get() * 1000 / RT_TICK_PER_SECOND; tmp = spi_init.SPI_Mode; if(!((tmp == SPI_Mode_Master) && (spi_init.SPI_Direction == SPI_Direction_2Lines_FullDuplex))) { errorcode = 2; goto error; } if((pTxData == NULL) || (pRxData == NULL) || (Size == 0)) { errorcode = 1; goto error; } TxXferCount = Size; RxXferCount = Size; /* Check if the SPI is already enabled */ if((spi_instance->CR1 &SPI_CR1_SPE) != SPI_CR1_SPE) { /* Enable SPI peripheral */ spi_instance->CR1 |= SPI_CR1_SPE; } /* Transmit and Receive data in 16 Bit mode */ if(spi_init.SPI_DataSize == SPI_DataSize_16b) { if((spi_init.SPI_Mode == SPI_Mode_Slave) || (TxXferCount == 0x01U)) { SPI_I2S_ReceiveData(spi_instance); spi_instance->DR = *((uint16_t *)pTxData); pTxData += sizeof(uint16_t); TxXferCount--; } while ((TxXferCount > 0U) || (RxXferCount > 0U)) { /* Check TXE flag */ if(txallowed && (TxXferCount > 0U) && (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_TXE) == SET)) { SPI_I2S_ReceiveData(spi_instance); spi_instance->DR = *((uint16_t *)pTxData); pTxData += sizeof(uint16_t); TxXferCount--; /* Next Data is a reception (Rx). Tx not allowed */ txallowed = 0U; } /* Check RXNE flag */ if((RxXferCount > 0U) && (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_RXNE) == SET)) { *((uint16_t *)pRxData) = spi_instance->DR; pRxData += sizeof(uint16_t); RxXferCount--; /* Next Data is a Transmission (Tx). Tx is allowed */ txallowed = 1U; } if((Timeout != 0xFFFFFFFFU) && ((rt_tick_get() * 1000 / RT_TICK_PER_SECOND-tickstart) >= Timeout)) { errorcode = 3; goto error; } } } /* Transmit and Receive data in 8 Bit mode */ else { if((spi_init.SPI_Mode == SPI_Mode_Slave) || (TxXferCount == 0x01U)) { SPI_I2S_ReceiveData(spi_instance); *((__IO uint8_t*)&spi_instance->DR) = (*pTxData); pTxData += sizeof(uint8_t); TxXferCount--; } while((TxXferCount > 0U) || (RxXferCount > 0U)) { /* 等待发送缓冲区为空,TXE事件 */ if (txallowed && (TxXferCount > 0U) && (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_TXE) == SET)) { /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */ SPI_I2S_ReceiveData(spi_instance); *((__IO uint8_t*)&spi_instance->DR) = (*pTxData); pTxData += sizeof(uint8_t); TxXferCount--; txallowed = 0U; } if ((RxXferCount > 0U) && SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_RXNE) == SET) { *(uint8_t *)pRxData = spi_instance->DR; pRxData += sizeof(uint8_t); RxXferCount--; txallowed = 1U; } if((Timeout != 0xFFFFFFFFU) && ((rt_tick_get() * 1000 / RT_TICK_PER_SECOND-tickstart) >= Timeout)) { errorcode = 3; goto error; } } } /* Wait until TXE flag */ if(spi_wait_until_timeout(spi_init, spi_instance, SPI_FLAG_TXE, SET, Timeout, tickstart) != 0) { errorcode = 3; goto error; } /* Check Busy flag */ if(spi_wait_until_timeout(spi_init, spi_instance, SPI_FLAG_BSY, RESET, Timeout, tickstart) != 0) { errorcode = 1; goto error; } error : return errorcode; } int spi_receive(SPI_InitTypeDef spi_init, SPI_TypeDef *spi_instance, uint8_t *pData, uint16_t Size, uint32_t Timeout) { uint32_t tickstart = 0U; int errorcode = 0; __IO uint16_t RxXferCount; __IO uint32_t SPITimeout; if((spi_init.SPI_Mode == SPI_Mode_Master) && (spi_init.SPI_Direction == SPI_Direction_2Lines_FullDuplex)) { /* Call transmit-receive function to send Dummy data on Tx line and generate clock on CLK line */ return spi_transmitreceive(spi_init, spi_instance,pData,pData,Size,Timeout); } /* Init tickstart for timeout management*/ tickstart = rt_tick_get() * 1000 / RT_TICK_PER_SECOND; if((pData == NULL ) || (Size == 0)) { errorcode = 1; goto error; } RxXferCount = Size; /* Configure communication direction: 1Line */ if(spi_init.SPI_Direction == SPI_Direction_1Line_Rx) { spi_instance->CR1 &= (~SPI_CR1_BIDIOE); } /* Check if the SPI is already enabled */ if((spi_instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { /* Enable SPI peripheral */ spi_instance->CR1 |= SPI_CR1_SPE; } /* Receive data in 8 Bit mode */ if(spi_init.SPI_DataSize == SPI_DataSize_8b) { /* Transfer loop */ while(RxXferCount > 0U) { SPITimeout = 0x1000; /* 等待发送缓冲区为空,TXE事件 */ while (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_RXNE) == RESET) { if((SPITimeout--) == 0) { rt_kprintf("spi超时\n"); errorcode = 3; goto error; } } *(uint8_t *)pData = spi_instance->DR; pData += sizeof(uint8_t); RxXferCount--; } } else { /* Transfer loop */ while(RxXferCount > 0U) { SPITimeout = 0x1000; /* Check the RXNE flag */ while (SPI_I2S_GetFlagStatus(spi_instance, SPI_I2S_FLAG_RXNE) == RESET) { if((SPITimeout--) == 0) { rt_kprintf("spi超时\n"); errorcode = 3; goto error; } } *((uint16_t*)pData) = spi_instance->DR; pData += sizeof(uint16_t); RxXferCount--; } } /* Check the end of the transaction */ if((spi_init.SPI_Mode == SPI_Mode_Master)&&((spi_init.SPI_Direction == SPI_Direction_1Line_Rx)||(spi_init.SPI_Direction == SPI_Direction_2Lines_RxOnly))) { /* Disable SPI peripheral */ spi_instance->CR1 &= (~SPI_CR1_SPE); } error : return errorcode; } ``` 至此SPI驱动需要实现的功能基本已经完成,SPI总线在内核启动过程中会注册到内核。 当然还需要挂载SPI设备,如下: ``` rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef *cs_gpiox, uint16_t cs_gpio_pin) { RT_ASSERT(bus_name != RT_NULL); RT_ASSERT(device_name != RT_NULL); rt_err_t result; struct rt_spi_device *spi_device; struct stm32_hw_spi_cs *cs_pin; /* initialize the cs pin && select the slave*/ GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.GPIO_Pin = cs_gpio_pin; GPIO_Initure.GPIO_Mode = GPIO_Mode_OUT; GPIO_Initure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(cs_gpiox, &GPIO_Initure); GPIO_WriteBit(cs_gpiox, cs_gpio_pin, Bit_SET); /* attach the device to spi bus*/ spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device)); RT_ASSERT(spi_device != RT_NULL); cs_pin = (struct stm32_hw_spi_cs *)rt_malloc(sizeof(struct stm32_hw_spi_cs)); RT_ASSERT(cs_pin != RT_NULL); cs_pin->GPIOx = cs_gpiox; cs_pin->GPIO_Pin = cs_gpio_pin; result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin); if (result != RT_EOK) { LOG_E("%s attach to %s faild, %d\n", device_name, bus_name, result); } RT_ASSERT(result == RT_EOK); LOG_D("%s attach to %s done", device_name, bus_name); return result; } ``` 现在SPI设备就可以使用了,其中改写了很多HAL库封装的内容,以便让自己更好的理解SPI。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
存在即合理
这家伙很懒,什么也没写!
文章
3
回答
12
被采纳
0
关注TA
发私信
相关文章
1
BBB的SPI驱动
2
求个SPI上挂两个或多个设备的使用例子
3
SPI设备有个bug
4
spi flash 的fatfs使用一段时间后读写文件出现故障
5
SPI驱动
6
请教rt_spi_configure函数理解
7
SPI FLASH挂载的问题
8
SPI-FLASH 文件系统 SPIFFS
9
求助一个完整的 spi flash 驱动
10
关于同时使用文件系统与SPI FLASH的问题
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
FAL
RTC
rt-smart
I2C_IIC
cubemx
UART
ESP8266
WIZnet_W5500
BSP
ota在线升级
PWM
flash
packages_软件包
freemodbus
潘多拉开发板_Pandora
ADC
GD32
定时器
编译报错
flashDB
keil_MDK
socket
中断
rt_mq_消息队列_msg_queue
Debug
ulog
SFUD
msh
C++_cpp
at_device
本月问答贡献
出出啊
1524
个答案
343
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
818
个答案
179
次被采纳
crystal266
555
个答案
162
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
出出啊
1
篇文章
1
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
2
次点赞
crystal266
2
篇文章
1
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部