Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
rt_event_事件集
NXP 微控制器
NXP-MCXN947
NXP MCXN947测评之SPI实践 - BMI270 加速度计数据读取
发布于 2024-05-06 02:57:01 浏览:492
订阅该版
[tocm] # 前言 当我在公众号上看到NXP FRDM-MCXN947开发板的评测活动时,我不禁回忆起了我刚开始负责嵌入式软件开发的那段经历,那时我第一次接触的开发平台就是NXP。于是我决定加入评测小组,并非常幸运地被分配到了SPI评测的任务。 # MCXN947开发板 熊猫色的开发板包装中,主要藏着快速使用指引1本、开发板主体1块、USB-C数据线1根,跳线帽2只和包装清单一张。 ![Overview_800x600.jpg](https://oss-club.rt-thread.org/uploads/20240506/86a639d6afce2c3b41dc394a5167f275.jpg) 黑色的主板给人一种高端的感觉,用料扎实做工精细,背面使用4个橡胶腿代替了普通的铜螺柱感觉是非常人性化的设计。如果要说缺点,可能就是正面的三色LED灯颜色不够温和,闪的时候差点亮瞎了笔者的眼睛…… ![Forward_800x600.jpg](https://oss-club.rt-thread.org/uploads/20240506/41f956e3528c7069395f6214fe223bd0.jpg) # 评测方案 本方案旨在通过在FRDM-MCXN947开发板上实现BMI270加速度传感器数据的读取,来评估RT-thread对该平台SPI的支持情况。 ### 硬件方案 + 开发板主板通过板载3.3V电源为BMI270传感器的VDD和VDDIO供电 + BMI270传感器通过转接板连接到开发板的Flexcomm6_SPI口上 ![Setup_3.jpg](https://oss-club.rt-thread.org/uploads/20240506/9bfd0b71b1764ccd4a81c7bb52ed2655.jpg.webp) ### 软件方案 + 启动初始化Flexcomm6_SPI外设,配置波特率至8MHz(传感器最高支持10MHz)。 + 软件定时器控制轮询读取传感器加速度计数据,轮询频率为50Hz。 + 加速度计传感器配置+/-16G量程,1600Hz输出频率。 + 控制台串口打印加速度计原始数据(16bit整型数据)。 + 由板载Wakeup按钮(开发板正面右上角按钮)控制使能传感器和软件定时器。 + 本测试基于RT-thread代码仓库commit - 39e6b36bb02b431999bd8b91701d3761a56b3eb3 + 传感器驱动来源于Bosch Sensortec代码仓库 [BMI270 v2.86.1版本](https://github.com/boschsensortec/BMI270_SensorAPI/releases/tag/v2.86.1) #### 关于BMI270 (来源于ChatGPT v3.5) BMI270是一款集成了惯性测量单元(IMU)和传感器数据处理器的先进传感器。它是Bosch Sensortec在运动控制和导航领域的最新创新之一,被广泛用于智能手机、可穿戴设备、健康监测、虚拟现实和增强现实等领域。 以下是BMI270的一些主要特点和优势: - 小巧集成:BMI270采用了先进的封装技术,使其体积小巧,适用于空间受限的应用场景。 - 低功耗设计:Bosch Sensortec注重低功耗设计,BMI270在提供高性能的同时能够保持低功耗运行,使其适用于依赖于电池供电的设备。 - 多功能性:BMI270集成了三轴加速度计和三轴陀螺仪,同时还具备传感器数据处理器,能够提供精确的运动跟踪和姿态估计功能。 - 高性能运动跟踪:通过先进的传感器融合算法和数据处理技术,BMI270能够实现精确的运动跟踪,包括步数计数、姿态识别等功能。 - 高度集成:BMI270集成了传感器和处理器,简化了系统设计,减少了外部元件的数量,降低了系统成本。 # BMI270传感器SPI模块 本模块主要包含传感器和主控MCU的SPI通讯操作。 ## 配置并初始化SPI 这一块没有太多可以说的,按照软件实施模块中的描述,将波特率设置在8MHz,模式设置为3(CPOL=1, CPHA=1),手动控制片选引脚。需要注意的一点是,BMI270这款传感器,默认是使用I2C接口的,如果我们想要使用SPI接口,需要先将传感器片选引脚拉低再拉高(提供一个上升沿电平)。 ```c #define SENSOR_SPI_NAME "sens_spi" #define SENSOR_SPI_CS (3 * 32 + 23) /*! * @brief This function is to get sensor SPI device * * @return Pointer to sensor SPI device */ struct rt_spi_device* sensor_spi_device_get(void) { return (struct rt_spi_device *)rt_device_find(SENSOR_SPI_NAME); } /*! * @brief This function is to initialize flexcomm6_SPI for BMI270 sensor w/r * * @return Initialize result */ rt_err_t sensor_spi_init(void) { struct rt_spi_configuration cfg = { 0 }; cfg.data_width = 8; cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_3 | RT_SPI_MSB | RT_SPI_NO_CS; cfg.max_hz = 8 *1000 *1000; /* Attach chip select pin and apply name to SPI device */ rt_hw_spi_device_attach("spi6", SENSOR_SPI_NAME, SENSOR_SPI_CS); /* Get SPI device from name */ struct rt_spi_device *sensor_spi_dev = sensor_spi_device_get(); if (RT_NULL == sensor_spi_dev) { rt_kprintf("rt_device_find(%s) failed\n", SENSOR_SPI_NAME); return -RT_ERROR; } /* Apply configuration to SPI device */ rt_spi_configure(sensor_spi_dev, &cfg); /* Pull down and then pull up to switch sensor comm interface to SPI */ rt_spi_take(sensor_spi_dev); rt_spi_release(sensor_spi_dev); return RT_EOK; } ``` ## 传感器驱动读写函数 BMI270传感器驱动中的SPI读写接口需要我们根据具体的平台来单独实。传感器的一般操作方式是先写入要操作的寄存器地址,然后再读取或者写入相应的数据,所以这里我们采用RT-thread中的`rt_spi_send_then_recv()`方法和`rt_spi_send_then_send()`方法来实现。[RT-thread文档中心传送门](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi) ```c /*! * @brief This function is to read data from sensor regs * * @param[in] reg: Register address from which data is read. * @param[in] data: Pointer to data buffer where read data is stored. * @param[in] len: Number of bytes of data to be read. * @param[in] intf_ptr: Pointer to SPI interface device * * @return SPI read result * * @retval == RT_EOK -> Success * @retval != RT_EOK -> Failure */ rt_int8_t sensor_spi_read(rt_uint8_t reg, rt_uint8_t *data, rt_uint32_t len, void *intf_ptr) { /* Variable to define return result */ rt_err_t rslt; if (data == NULL || intf_ptr == NULL) { return -1; } /* Get device handler */ struct rt_spi_device *sensor_spi_dev = (struct rt_spi_device *)intf_ptr; /* Take CS pin */ /* No need take CS by user manually, this operation is took account into rt_spi_send_then_recv() */ // rslt = rt_spi_take(sensor_spi_dev); // if (RT_EOK != rslt) // { // rt_kprintf("rt_spi_take() failed, error code = %d", rslt); // return rslt; // } rslt = rt_spi_send_then_recv(sensor_spi_dev, ®, 1, data, len); if (RT_EOK != rslt) { rt_kprintf("rt_spi_send_then_recv() failed, error code = %d", rslt); return rslt; } /* Release CS pin */ /* No need release CS by user manually, this operation is took account into rt_spi_send_then_recv() */ // rslt = rt_spi_release(sensor_spi_dev); // if (RT_EOK != rslt) // { // rt_kprintf("rt_spi_release() failed, error code = %d", rslt); // return rslt; // } return RT_EOK; } /*! * @brief This function is to write data from sensor regs * * @param[in] reg: Register address from which data is write. * @param[in] data: Pointer to data buffer where write data is stored. * @param[in] len: Number of bytes of data to be write. * @param[in] intf_ptr: Pointer to SPI interface device * * @return SPI write result * * @retval == RT_EOK -> Success * @retval != RT_EOK -> Failure */ rt_int8_t sensor_spi_write(rt_uint8_t reg, const rt_uint8_t *data, rt_uint32_t len, void *intf_ptr) { /* Variable to define return result */ rt_err_t rslt; if (data == NULL || intf_ptr == NULL) { return -1; } /* Get device handler */ struct rt_spi_device *sensor_spi_dev = (struct rt_spi_device *)intf_ptr; /* Take CS pin */ /* No need take CS by user manually, this operation is took account into rt_spi_send_then_send() */ // rslt = rt_spi_take(sensor_spi_dev); // if (RT_EOK != rslt) // { // rt_kprintf("rt_spi_take() failed, error code = %d", rslt); // return rslt; // } rslt = rt_spi_send_then_send(sensor_spi_dev, ®, 1, data, len); if (RT_EOK != rslt) { rt_kprintf("rt_spi_send_then_send() failed, error code = %d", rslt); return rslt; } /* Release CS pin */ /* No need release CS by user manually, this operation is took account into rt_spi_send_then_send() */ // rslt = rt_spi_release(sensor_spi_dev); // if (RT_EOK != rslt) // { // rt_kprintf("rt_spi_release() failed, error code = %d", rslt); // return rslt; // } return RT_EOK; } ``` # BMI270传感器接口模块 本模块主要包含传感器轮询线程、传感器初始化方法、使能和关闭传感器及轮询时钟方法。 ## 传感器轮询线程 线程间的同步采用rt_event事件集 - 当软件定时器触发中断时,中断会给线程发送SENSOR_EVT_POLLING事件,轮询线程会读取传感器数据,并打印在工作台中。 - 当用户点击Wakeup按钮时,GPIO中断会给线程发送SENSOR_EVT_ENABLE或者SENSOR_EVT_DISABLE事件,轮询线程会开启或关闭传感器及定时器。 ```c /*! Earth's gravity in m/s^2 */ #define GRAVITY_EARTH (9.80665f) /*! Scheduler events */ #define SENSOR_EVT_POLLING (1 << 0) #define SENSOR_EVT_ENABLE (1 << 1) #define SENSOR_EVT_DISABLE (1 << 2) static rt_uint8_t sensor_active_flag; static rt_thread_t sensor_scheduler_tid; static rt_timer_t sensor_timer; static rt_sem_t sensor_sem; static rt_event_t sensor_event; /*! * @brief This function is to prepare sensor scheduler application */ rt_err_t sensor_scheduler_app(void) { rt_err_t ret = RT_EOK; sensor_event = rt_event_create("sens_evt", RT_IPC_FLAG_FIFO); if (sensor_event == RT_NULL) { rt_kprintf("sensor event create failed!\n"); return -RT_ERROR; } /* Create scheduler_app thread */ sensor_scheduler_tid = rt_thread_create("sens_app", sensor_scheduler_entry, RT_NULL, 1024, 4, 1000); if (sensor_scheduler_tid == RT_NULL) { rt_kprintf("sensor scheduler app thread create failed!\n"); // rt_sem_delete(sensor_sem); rt_event_delete(sensor_event); return -RT_ERROR; } /* Startup scheduler_app thread */ ret = rt_thread_startup(sensor_scheduler_tid); if (ret != RT_EOK) { rt_kprintf("sensor scheduler app thread startup failed, error code = %d!\n", ret); // rt_sem_delete(sensor_sem); rt_event_delete(sensor_event); return -RT_ERROR; } /* Create sensor polling timer, polling rate is 50Hz */ sensor_timer = rt_timer_create("sens_tmr", sensor_timer_timeout, RT_NULL, 20, RT_TIMER_FLAG_PERIODIC); if (sensor_timer == RT_NULL) { rt_kprintf("sensor timer create failed!\n"); // rt_sem_delete(sensor_sem); rt_event_delete(sensor_event); return -RT_ERROR; } return RT_EOK; } /*! * @brief Main process function to read sensor data */ static void sensor_scheduler_entry(void *parameter) { (void)parameter; /* Status of api are returned to this variable. */ rt_int8_t rslt; /* Structure to define type of sensor and their respective data. */ struct bmi2_sens_data sens_data = { { 0 } }; /* Sensor data in unit mps2 */ float x, y, z; /* Variable to define event set */ rt_uint32_t event; /* Assign accel sensor to variable. */ rt_uint8_t sensor_list = BMI2_ACCEL; while (1) { if (rt_event_recv(sensor_event, SENSOR_EVT_POLLING | SENSOR_EVT_ENABLE | SENSOR_EVT_DISABLE, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &event) == RT_EOK) { if (event & SENSOR_EVT_POLLING) { rslt = bmi2_get_sensor_data(&sens_data, &dev); if (rslt) { rt_kprintf("bmi2_get_sensor_data() failed, error code = %d\n", rslt); continue; } else if (sens_data.status & BMI2_DRDY_ACC) { /* Converting lsb to meter per second squared for 16 bit accelerometer at 2G range. */ x = lsb_to_mps2(sens_data.acc.x, 2, 16); y = lsb_to_mps2(sens_data.acc.y, 2, 16); z = lsb_to_mps2(sens_data.acc.z, 2, 16); rt_kprintf("Sensor Data: %d, %d, %d\n", sens_data.acc.x, sens_data.acc.y, sens_data.acc.z); } } if (event & SENSOR_EVT_ENABLE) { rslt = bmi2_sensor_enable(&sensor_list, 1, &dev); if (rslt) { rt_kprintf("bmi2_sensor_enable() failed, error code = %d\n", rslt); } rslt = rt_timer_start(sensor_timer); if (rslt) { rt_kprintf("rt_timer_start failed, error code = %d!\n", rslt); } sensor_active_flag = 1; rt_kprintf("BMI270 accel enabled\r\n"); } if (event & SENSOR_EVT_DISABLE) { rslt = bmi2_sensor_disable(&sensor_list, 1, &dev); if (rslt) { rt_kprintf("bmi2_sensor_disable() failed, error code = %d\n", rslt); } rslt = rt_timer_stop(sensor_timer); if (rslt) { rt_kprintf("rt_timer_stop failed, error code = %d!\n", rslt); } sensor_active_flag = 0; rt_kprintf("BMI270 accel disabled\r\n"); } } } } /*! * @brief Interrupt function to sensor timer timeout */ static void sensor_timer_timeout(void *parameter) { (void)parameter; rt_event_send(sensor_event, SENSOR_EVT_POLLING); } /*! * @brief This function converts lsb to meter per second squared for 16 bit accelerometer at * range 2G, 4G, 8G or 16G. */ static float lsb_to_mps2(int16_t val, float g_range, uint8_t bit_width) { float half_scale = ((float)(1 << bit_width) / 2.0f); return (GRAVITY_EARTH * val * g_range) / half_scale; } ``` ## 传感器控制接口 传感器控制接口主要包含以下几个函数: + sensor_intf_init: 主要用户传感器的初始化及一些基本配置 + sensor_enable: 通知轮询线程使能传感器和软件定时器 + sensor_disable: 通知轮询线程关闭传感器和软件定时器 ```c /*! Macro that defines read write length */ #define READ_WRITE_LEN UINT8_C(46) static struct bmi2_dev dev; rt_err_t sensor_intf_init(void) { /* Status of api are returned to this variable. */ rt_int8_t rslt; /* Structure to define accelerometer configuration. */ struct bmi2_sens_config config; /* Get SPI device from name */ void *sensor_spi_dev = (void *)sensor_spi_device_get(); if (RT_NULL == sensor_spi_dev) { rt_kprintf("sensor_spi_device_get() failed\n"); return -RT_ERROR; } memset(&dev, 0, sizeof(dev)); dev.intf = BMI2_SPI_INTF; dev.read = sensor_spi_read; dev.write = sensor_spi_write; dev.delay_us = sensor_intf_delay_us; dev.intf_ptr = sensor_spi_dev; dev.read_write_len = READ_WRITE_LEN; /* Initialize bmi270. */ rslt = bmi270_init(&dev); if (rslt) { rt_kprintf("bmi270_init() failed, error code = %d\n", rslt); return -RT_ERROR; } /* Disable advanced power save mode */ rslt = bmi2_set_adv_power_save(BMI2_DISABLE, &dev); if (rslt) { rt_kprintf("bmi2_set_adv_power_save() failed, error code = %d\n", rslt); return -RT_ERROR; } /* Configure the type of feature. */ config.type = BMI2_ACCEL; /* Get default configurations for the type of feature selected. */ rslt = bmi2_get_sensor_config(&config, 1, &dev); if (rslt) { rt_kprintf("bmi2_get_sensor_config() failed, error code = %d\n", rslt); return -RT_ERROR; } /* Set Output Data Rate */ config.cfg.acc.odr = BMI2_ACC_ODR_200HZ; /* Gravity range of the sensor (+/- 2G, 4G, 8G, 16G). */ config.cfg.acc.range = BMI2_ACC_RANGE_16G; /* The bandwidth parameter is used to configure the number of sensor samples that are averaged * if it is set to 2, then 2^(bandwidth parameter) samples * are averaged, resulting in 4 averaged samples. * Note1 : For more information, refer the datasheet. * Note2 : A higher number of averaged samples will result in a lower noise level of the signal, but * this has an adverse effect on the power consumed. */ config.cfg.acc.bwp = BMI2_ACC_NORMAL_AVG4; /* Enable the filter performance mode where averaging of samples * will be done based on above set bandwidth and ODR. * There are two modes * 0 -> Ultra low power mode * 1 -> High performance mode(Default) * For more info refer datasheet. */ config.cfg.acc.filter_perf = BMI2_PERF_OPT_MODE; /* Set the accel configurations. */ rslt = bmi2_set_sensor_config(&config, 1, &dev); if (rslt) { rt_kprintf("bmi2_set_sensor_config() failed, error code = %d\n", rslt); return -RT_ERROR; } return RT_EOK; } /*! * @brief This function is to enable sensor and startup sensor polling timer * * @return Enable result */ rt_err_t sensor_enable(void) { return rt_event_send(sensor_event, SENSOR_EVT_ENABLE); } /*! * @brief This function is to disable sensor and stop sensor polling timer * * @return Disable result */ rt_err_t sensor_disable(void) { return rt_event_send(sensor_event, SENSOR_EVT_DISABLE); } /*! * @brief Sensor delay function * * @param[in] period: Delay period in us * @param[in] intf_ptr: Reserved * */ static void sensor_intf_delay_us(rt_uint32_t period, void *intf_ptr) { (void)intf_ptr; if (period > 1000) { rt_thread_mdelay((period + 500) / 1000); } else { rt_hw_us_delay(period); } } ``` ## 主程序和Wakeup GPIO中断 这部分主要复用RT-thread本开发板BSP中的内容,对部分代码进行一些修改: + 主程序中调用SPI初始化方法 + 在GPIO中断响应函数中调用sensor_enable和sensor_disable接口 ```c int main(void) { ... rt_err_t ret = sensor_spi_init(); if (ret) { rt_kprintf("sensor_spi_init failed, error code = %d\n", ret); } ret = sensor_intf_init(); if (ret) { rt_kprintf("sensor_intf_init failed, error code = %d\n", ret); } ret = sensor_scheduler_app(); if (ret) { rt_kprintf("sensor_scheduler_app failed, error code = %d\n", ret); } while (1) { rt_pin_write(LEDB_PIN, PIN_HIGH); /* Set GPIO output 1 */ rt_thread_mdelay(500); /* Delay 500mS */ rt_pin_write(LEDB_PIN, PIN_LOW); /* Set GPIO output 0 */ rt_thread_mdelay(500); /* Delay 500mS */ } } static void sw_pin_cb(void *args) { if (sensor_status_get()) { sensor_disable(); } else { sensor_enable(); } } ``` # 测评结论 RT-thread针对MCXN947平台的BSP包,能够基本实现SPI通讯功能。在硬件初始化阶段,SPI会被统一重置为Master默认属性,波特率会被配置为24MHz。 但是由于目前并没有真正实现rt_spi_ops的.configure接口,用户如果想调用rt_spi_configure方法配置SPI的属性,是无法生效的。 (笔者将Flexcomm6_SPI接口配置为8MHz一开始是没有生效的。) 目前源代码如下: ```c static rt_err_t spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) { rt_err_t ret = RT_EOK; // struct lpc_spi *spi = RT_NULL; // spi = (struct lpc_spi *)(device->bus->parent.user_data); // ret = lpc_spi_init(spi->SPIx, cfg); return ret; } static struct rt_spi_ops lpc_spi_ops = { .configure = spi_configure, .xfer = spixfer }; ``` 笔者实现了一个基础版本,如有需要的朋友,可以复制到drv_spi.c文件中: ```c /*! * @brief Initialize spi device via user configuration * * @param[in] base: Handler of low power SPI * @param[in] cfg: Structure to RT-thread spi configuration definition * * @return Initialize result * @retval == RT_EOK -> Success * @retval != RT_EOK -> Failed */ static rt_err_t lpc_spi_init(LPSPI_Type *base, struct rt_spi_configuration *cfg) { /* Find SPI index via handler */ int idx = 0; for(idx = 0; idx < ARRAY_SIZE(lpc_obj); idx++) { if (lpc_obj[idx].LPSPIx == base) { break; } } if (idx == ARRAY_SIZE(lpc_obj)) { return -RT_ERROR; } /* Load default configuration for master spi device */ lpspi_master_config_t masterConfig = {0}; LPSPI_MasterGetDefaultConfig(&masterConfig); /* TODO: Need to check, whether NXP SPI supports 32bits or not */ if(cfg->data_width != 8 && cfg->data_width != 16) { cfg->data_width = 8; } masterConfig.baudRate = cfg->max_hz; masterConfig.bitsPerFrame = cfg->data_width; if(cfg->mode & RT_SPI_MSB) { masterConfig.direction = kLPSPI_MsbFirst; } else { masterConfig.direction = kLPSPI_LsbFirst; } if(cfg->mode & RT_SPI_CPHA) { masterConfig.cpha = kLPSPI_ClockPhaseSecondEdge; } else { masterConfig.cpha = kLPSPI_ClockPhaseFirstEdge; } if(cfg->mode & RT_SPI_CPOL) { masterConfig.cpol = kLPSPI_ClockPolarityActiveLow; } else { masterConfig.cpol = kLPSPI_ClockPolarityActiveHigh; } masterConfig.pcsToSckDelayInNanoSec = 1000000000U / masterConfig.baudRate * 1U; masterConfig.lastSckToPcsDelayInNanoSec = 1000000000U / masterConfig.baudRate * 1U; masterConfig.betweenTransferDelayInNanoSec = 1000000000U / masterConfig.baudRate * 1U; LPSPI_MasterInit(base, &masterConfig, CLOCK_GetFreq(lpc_obj[idx].clock_name)); return RT_EOK; } static rt_err_t spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) { rt_err_t ret = RT_EOK; struct lpc_spi *spi = RT_NULL; spi = (struct lpc_spi *)(device->bus->parent.user_data); ret = lpc_spi_init(spi->LPSPIx, cfg); return ret; } ``` 此外,当前代码中存在一个错误, SPI6结构体实例中,缺少了'},',需要用户自行补上: ```c #ifdef BSP_USING_SPI6 { .LPSPIx = LPSPI6, .clock_attach_id = kFRO_HF_DIV_to_FLEXCOMM6, .clock_div_name = kCLOCK_DivFlexcom6Clk, .clock_name = kCLOCK_FroHf, .tx_dma_request = kDmaRequestMuxLpFlexcomm6Tx, .rx_dma_request = kDmaRequestMuxLpFlexcomm6Rx, .DMAx = DMA0, .tx_dma_chl = 4, .rx_dma_chl = 5, .name = "spi6", }, // 代码仓库中,缺失此行 #endif /* BSP_USING_SPI6 */ ``` 在NXP的设置选项中,片选引脚是高电平有效还是低电平有效是可以被配置的,但是在BSP包中,这个属性是无法被设置的,原因如下: + 在rt_spi_configuration结构体中,没有片选引脚有效性的属性 + 在spixfer方法中,拉片选的时候,并没有对片选引脚有效性的属性的判断 如果在硬件中,有器件是需要片选引脚为高电平生效的,需要修改这个函数的实现方式。 ```c static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message) { ... if(message->cs_take) { rt_pin_write(cs->pin, PIN_LOW); // 此处写死为低电平有效 } ... if(message->cs_release) { rt_pin_write(cs->pin, PIN_HIGH); } ... } ``` # 其他 如果有同学在使用scons对代码进行编译的时候,遇到这个错误:`ValueError: unsupported pickle protocol: 4`,可以尝试删除文件夹下面的`.sconsign.dblite`。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
uniquechuck
这家伙很懒,什么也没写!
文章
1
回答
0
被采纳
0
关注TA
发私信
相关文章
1
试贴-消灭0主题
2
LPC M4的一些资料
3
LPC4088的临时分支
4
lpc1788 ad 不稳定
5
1788 LCD控制器缓冲区字节问题
6
一起来学习LPC4088吧
7
上传LPC4088的realtouch主工程
8
RealBoard 4088预定帖 [第一批板子不多,预定提前结束]
9
晒RealBoard LPC4088开箱照啦,速带小板凳前来围观
10
4088主程序需要的SD卡资源
推荐文章
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
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
ulog
C++_cpp
at_device
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
8
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部