当前位置: 首页 > news >正文

BetaFlight模块设计之三十六:SoftSerial

BetaFlight模块设计之三十六:SoftSerial

  • 1. 源由
  • 2. API接口
    • 2.1 openSoftSerial
    • 2.2 onSerialRxPinChange
    • 2.3 onSerialTimerOverflow
    • 2.4 processTxState
    • 2.5 processRxState
  • 3. 辅助函数
    • 3.1 applyChangedBits
    • 3.2 extractAndStoreRxByte
    • 3.3 prepareForNextRxByte
  • 4. 总结

1. 源由

鉴于Betaflight关于STM32F405 SBUS协议兼容硬件电气特性问题,从程序代码上看,软串口应该能够采用定时器、中断的方式进行电平协议的解析。

但是从实测Betaflight4.4.2固件的角度看,又无法使用,怀疑可能存在以下问题:

  1. 配置问题
  2. 代码移植BUG(unified_target ==> config)
  3. 代码不支持

所以尝试整理下SoftSerial代码结构,通过对整体代码的了解,能否找出其中的一些深层次原因。

2. API接口

从对外接口的角度看,主要有以下API:

  • 打开软件串口openSoftSerial
  • 底层串行信号电平变更处理onSerialRxPinChange
  • 底层串行信号超市处理onSerialTimerOverflow
  • 后端Tx状态处理processTxState
  • 后端Rx状态处理processRxState
serialPort_t *openSoftSerial(softSerialPortIndex_e portIndex, serialReceiveCallbackPtr rxCallback, void *rxCallbackData, uint32_t baud, portMode_e mode, portOptions_e options)
void onSerialRxPinChange(timerCCHandlerRec_t *cbRec, captureCompare_t capture)
void onSerialTimerOverflow(timerOvrHandlerRec_t *cbRec, captureCompare_t capture)
void processTxState(softSerial_t *softSerial)
void processRxState(softSerial_t *softSerial)

2.1 openSoftSerial

根据资源进行配置:

  • 【Hardware】GPIO:Tx/Rx/SERIAL_INVERTED
  • 【Hardware】TIMER
  • 【Hardware】Interrupt:ICPOLARITY_RISING/ICPOLARITY_FALLING
  • 【Software】Buffer
  • 【Software】Callback:onSerialRxPinChange(edgeCb)/onSerialTimerOverflow(overCb)/rxCallback
openSoftSerial││   // get serial port description├──> softSerial_t *softSerial = &(softSerialPorts[portIndex]);││   // get serial port rx/tx ioTag├──> ioTag_t tagRx = softSerialPinConfig()->ioTagRx[portIndex];├──> ioTag_t tagTx = softSerialPinConfig()->ioTagTx[portIndex];││   // one wire(Sbus etc.) or two wire softserial(UART etc.)├──> const timerHardware_t *timerTx = timerAllocate(tagTx, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));├──> const timerHardware_t *timerRx = (tagTx == tagRx) ? timerTx : timerAllocate(tagRx, OWNER_SOFTSERIAL_RX, RESOURCE_INDEX(portIndex));││   // get serial port rx/tx IO_t├──> IO_t rxIO = IOGetByTag(tagRx);├──> IO_t txIO = IOGetByTag(tagTx);││   // timer & io set├──> <options & SERIAL_BIDIR> // bi-direction configuration│   ├──> <!timerTx || (timerTx->output & TIMER_OUTPUT_N_CHANNEL)>│   │   │   // If RX and TX pins are both assigned, we CAN use either with a timer.│   │   │   // However, for consistency with hardware UARTs, we only use TX pin,│   │   │   // and this pin must have a timer, and it should not be N-Channel.│   │   └──> return NULL;│   ├──> softSerial->timerHardware = timerTx;│   ├──> softSerial->txIO = txIO;│   ├──> softSerial->rxIO = txIO;│   └──> IOInit(txIO, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));├──> < else > // unidirection configuration│   ├──> <mode & MODE_RX>│   │   ├──> <!timerRx || (timerRx->output & TIMER_OUTPUT_N_CHANNEL)>│   │   │   │   // Need a pin & a timer on RX. Channel should not be N-Channel.│   │   │   └──> return NULL;│   │   ├──> softSerial->rxIO = rxIO;│   │   ├──> softSerial->timerHardware = timerRx;│   │   └──> <!((mode & MODE_TX) && rxIO == txIO)>│   │       └──> IOInit(rxIO, OWNER_SOFTSERIAL_RX, RESOURCE_INDEX(portIndex));│   └──> <mode & MODE_TX>│       ├──> <!tagTx>│       │   │   // Need a pin on TX│       │   └──> return NULL;│       ├──> softSerial->txIO = txIO;│       ├──> <!(mode & MODE_RX)>│       │   ├──> <!timerTx> return NULL;│       │   │   // TX Simplex, must have a timer│       │   └──> softSerial->timerHardware = timerTx;│       ├──> < else >  // Duplex│       │   └──> softSerial->exTimerHardware = timerTx;│       └──> IOInit(txIO, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));││   // port configuration├──> softSerial->port.vTable = &softSerialVTable;├──> softSerial->port.baudRate = baud;├──> softSerial->port.mode = mode;├──> softSerial->port.options = options;├──> softSerial->port.rxCallback = rxCallback;├──> softSerial->port.rxCallbackData = rxCallbackData;│├──> resetBuffers(softSerial);│├──> softSerial->softSerialPortIndex = portIndex;│├──> softSerial->transmissionErrors = 0;├──> softSerial->receiveErrors = 0;│├──> softSerial->rxActive = false;├──> softSerial->isTransmittingData = false;││   // Configure master timer (on RX); time base and input capture├──> serialTimerConfigureTimebase(softSerial->timerHardware, baud);├──> timerChConfigIC(softSerial->timerHardware, (options & SERIAL_INVERTED) ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);││   // Initialize callbacks├──> timerChCCHandlerInit(&softSerial->edgeCb, onSerialRxPinChange);├──> timerChOvrHandlerInit(&softSerial->overCb, onSerialTimerOverflow);││   // Configure bit clock interrupt & handler.│   // If we have an extra timer (on TX), it is initialized and configured│   // for overflow interrupt.│   // Receiver input capture is configured when input is activated.├──> <(mode & MODE_TX) && softSerial->exTimerHardware && softSerial->exTimerHardware->tim != softSerial->timerHardware->tim>│   ├──> softSerial->timerMode = TIMER_MODE_DUAL;│   ├──> serialTimerConfigureTimebase(softSerial->exTimerHardware, baud);│   ├──> timerChConfigCallbacks(softSerial->exTimerHardware, NULL, &softSerial->overCb);│   └──> timerChConfigCallbacks(softSerial->timerHardware, &softSerial->edgeCb, NULL);├──> < else >│   ├──> softSerial->timerMode = TIMER_MODE_SINGLE;│   └──> timerChConfigCallbacks(softSerial->timerHardware, &softSerial->edgeCb, &softSerial->overCb);│├──> <USE_HAL_DRIVER>│   └──> softSerial->timerHandle = timerFindTimerHandle(softSerial->timerHardware->tim);││   // antivate port├──> <!(options & SERIAL_BIDIR)>│   ├──> serialOutputPortActivate(softSerial);│   └──> setTxSignal(softSerial, ENABLE);├──> serialInputPortActivate(softSerial);└──> return &softSerial->port;

2.2 onSerialRxPinChange

通过边沿中断记录bit数据流。

onSerialRxPinChange├──> softSerial_t *self = container_of(cbRec, softSerial_t, edgeCb);├──> bool inverted = self->port.options & SERIAL_INVERTED;│├──> <(self->port.mode & MODE_RX) == 0>│   └──> return;  // 无接收模式,直接返回│├──> <self->isSearchingForStartBit>│   │  // Synchronize the bit timing so that it will interrupt at the center│   │  // of the bit period.│   ├──> <USE_HAL_DRIVER>│   │   └──> __HAL_TIM_SetCounter(self->timerHandle, __HAL_TIM_GetAutoreload(self->timerHandle) / 2);│   ├──> <else>│   │   └──> TIM_SetCounter(self->timerHardware->tim, self->timerHardware->tim->ARR / 2);│   ││   │  // For a mono-timer full duplex configuration, this may clobber the│   │  // transmission because the next callback to the onSerialTimerOverflow│   │  // will happen too early causing transmission errors.│   │  // For a dual-timer configuration, there is no problem.│   ├──> <(self->timerMode != TIMER_MODE_DUAL) && self->isTransmittingData>│   │   └──> self->transmissionErrors++;│   ││   ├──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_FALLING : ICPOLARITY_RISING, 0);│   ├──> <defined(STM32F7) || defined(STM32H7) || defined(STM32G4)>│   │   └──> serialEnableCC(self);│   ││   ├──> self->rxEdge = LEADING;│   ││   ├──> self->rxBitIndex = 0;│   ├──> self->rxLastLeadingEdgeAtBitIndex = 0;│   ├──> self->internalRxBuffer = 0;│   ├──> self->isSearchingForStartBit = false;│   └──> return;││   // handle leveled signal├──> <self->rxEdge == LEADING>│   └──> self->rxLastLeadingEdgeAtBitIndex = self->rxBitIndex;├──>  applyChangedBits(self);│├──> <self->rxEdge == TRAILING>│   ├──> self->rxEdge = LEADING;│   └──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_FALLING : ICPOLARITY_RISING, 0);├──> < else >│   ├──> self->rxEdge = TRAILING;│   └──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);└──> <defined(STM32F7) || defined(STM32H7) || defined(STM32G4)>└──> serialEnableCC(self);

2.3 onSerialTimerOverflow

串行数据从原理上属于字符流协议,从实际应用角度,还是一包一包的数据(通常不会密集到头尾相连)。

因此,超时机制相当于处理:

  • 数据帧
  • 异常中断
onSerialTimerOverflow├──> softSerial_t *self = container_of(cbRec, softSerial_t, overCb);├──> <self->port.mode & MODE_TX> processTxState(self);└──> <self->port.mode & MODE_RX> processRxState(self);

2.4 processTxState

Tx数据处理存在三种情况:

  • 发送数据前处理
  • 发送数据
  • 发送数据后处理
processTxState│   // 发送数据前处理├──> <!softSerial->isTransmittingData>│   ├──> <isSoftSerialTransmitBufferEmpty((serialPort_t *)softSerial)>│   │   │   // Transmit buffer empty.│   │   │   // Start listening if not already in if half-duplex│   │   ├──> <!softSerial->rxActive && softSerial->port.options & SERIAL_BIDIR) {│   │   │   ├──> serialOutputPortDeActivate(softSerial);│   │   │   └──> serialInputPortActivate(softSerial);│   │   └──> return;│   │    │   │   // data to send│   ├──> uint8_t byteToSend = softSerial->port.txBuffer[softSerial->port.txBufferTail++];│   ├──> <softSerial->port.txBufferTail >= softSerial->port.txBufferSize>│   │   └──> softSerial->port.txBufferTail = 0;│   │   │   │   // build internal buffer, MSB = Stop Bit (1) + data bits (MSB to LSB) + start bit(0) LSB│   ├──> softSerial->internalTxBuffer = (1 << (TX_TOTAL_BITS - 1)) | (byteToSend << 1);│   ├──> softSerial->bitsLeftToTransmit = TX_TOTAL_BITS;│   ├──> softSerial->isTransmittingData = true;│   └──> <softSerial->rxActive && (softSerial->port.options & SERIAL_BIDIR)>│       │   // Half-duplex: Deactivate receiver, activate transmitter│       ├──> serialInputPortDeActivate(softSerial);│       ├──> serialOutputPortActivate(softSerial);│       ││       │   // Start sending on next bit timing, as port manipulation takes time,│       │   // and continuing here may cause bit period to decrease causing sampling errors│       │   // at the receiver under high rates.│       │   // Note that there will be (little less than) 1-bit delay; take it as "turn around time".│       │   // XXX We may be able to reload counter and continue. (Future work.)│       └──> return;││   // 发送bit数据:高/低 电平├──> <softSerial->bitsLeftToTransmit>│   ├──> mask = softSerial->internalTxBuffer & 1;│   ├──> softSerial->internalTxBuffer >>= 1;│   ││   ├──> setTxSignal(softSerial, mask);│   ├──> softSerial->bitsLeftToTransmit--;│   └──> return;││   // 发送数据后处理└──> softSerial->isTransmittingData = false;

2.5 processRxState

RX_TOTAL_BITS 10 bits format: start bit + 8 bits for one byte + stop bit

在这里插入图片描述

processRxState│   //Start bit处理├──> <softSerial->isSearchingForStartBit>│   └──> return;├──> softSerial->rxBitIndex++;││   //1 Byte数据处理├──> <softSerial->rxBitIndex == RX_TOTAL_BITS - 1>│   ├──> applyChangedBits(softSerial);│   └──> return;│   //Stop bit处理└──> <softSerial->rxBitIndex == RX_TOTAL_BITS>├──> softSerial->rxEdge == TRAILING>│   └──> softSerial->internalRxBuffer |= STOP_BIT_MASK;├──> extractAndStoreRxByte(softSerial);└──> prepareForNextRxByte(softSerial);

注:上述函数过程存在10bit缺损卡死的情况,代码还不够robust。

3. 辅助函数

3.1 applyChangedBits

1~9 bit数据将通过该函数进行存储,最后10bit数据将在processRxState中进行保存。

applyChangedBits└──> <softSerial->rxEdge == TRAILING>└──> for (bitToSet = softSerial->rxLastLeadingEdgeAtBitIndex; bitToSet < softSerial->rxBitIndex; bitToSet++)└──> softSerial->internalRxBuffer |= 1 << bitToSet;

3.2 extractAndStoreRxByte

从10 bit格式中抽取1Byte有效数据。

extractAndStoreRxByte│   //仅TX模式,无需进行任何接收字节的保存工作├──> <(softSerial->port.mode & MODE_RX) == 0>│   └──> return;│   ├──> uint8_t haveStartBit = (softSerial->internalRxBuffer & START_BIT_MASK) == 0;├──> uint8_t haveStopBit = (softSerial->internalRxBuffer & STOP_BIT_MASK) == 1;│  │   //起止bit位,若一项不符合规格,则丢弃数据├──> <!haveStartBit || !haveStopBit>│   ├──> softSerial->receiveErrors++;│   └──> return;│  │   //保存1Byte数据├──> uint8_t rxByte = (softSerial->internalRxBuffer >> 1) & 0xFF;│  ├──> <softSerial->port.rxCallback> //回调接收函数│   └──> softSerial->port.rxCallback(rxByte, softSerial->port.rxCallbackData);└──> < else > //无接收注册函数情况下,将数据存入缓冲buffer中,并采用循环方式覆盖保存├──> softSerial->port.rxBuffer[softSerial->port.rxBufferHead] = rxByte;└──> softSerial->port.rxBufferHead = (softSerial->port.rxBufferHead + 1) % softSerial->port.rxBufferSize;

3.3 prepareForNextRxByte

收录下一字节数据做预处理工作。

prepareForNextRxByte├──> softSerial->rxBitIndex = 0;├──> softSerial->isSearchingForStartBit = true;└──> <softSerial->rxEdge == LEADING>├──> softSerial->rxEdge = TRAILING;├──> timerChConfigIC(softSerial->timerHardware, (softSerial->port.options & SERIAL_INVERTED) ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);└──> serialEnableCC(softSerial);

4. 总结

SoftSerial代码角度,采用定时器、边沿中断的方式,随机使用CPU资源。如果应用在高速、大数据量通信场景,将会影响和打扰CPU正常业务逻辑,尤其是在CPU资源紧张时。

相关文章:

BetaFlight模块设计之三十六:SoftSerial

BetaFlight模块设计之三十六&#xff1a;SoftSerial 1. 源由2. API接口2.1 openSoftSerial2.2 onSerialRxPinChange2.3 onSerialTimerOverflow2.4 processTxState2.5 processRxState 3. 辅助函数3.1 applyChangedBits3.2 extractAndStoreRxByte3.3 prepareForNextRxByte 4. 总结…...

PC访问华为昇腾开发板的摸索过程

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 最近要折腾华为昇腾开发板&#xff08;官方名称叫&#xff1a;Atlas 200I DK&#xff09;。先是按照官方教程折腾&#xff1a;Atlas200DK环境部署。我发现…...

C++学习之路(六)C++ 实现简单的工具箱系统命令行应用 - 示例代码拆分讲解

简单的工具箱系统示例介绍: 这个示例展示了一个简单的工具箱框架&#xff0c;它涉及了几个关键概念和知识点&#xff1a; 面向对象编程 (OOP)&#xff1a;使用了类和继承的概念。Tool 是一个纯虚类&#xff0c;CalculatorTool 和 FileReaderTool 是其派生类。 多态&#xff1…...

redis运维(十四) hash缓存案例

一 缓存案例 ① 需求 ② 个人理解 策略&#xff1a;不更新缓存&#xff0c;而是删除缓存大部分观点认为&#xff1a;1、做缓存不应该是去更新缓存,而是应该删除缓存2、然后由下个请求去缓存,发现不存在后再读取数据库,写入redis缓存 高并发场景下,到底先更新缓存还是先更…...

Rust UI开发(三):iced如何打开图片(对话框)并在窗口显示图片?

注&#xff1a;此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库&#xff0c;用于为rust语言程序构建UI界面。 这是一个系列博文&#xff0c;本文是第三篇&#xff0c;前两篇的链接&#xff1a; 1、Rust UI开发&#xff08;一&#xff09;&#xff1a;使用iced构建…...

网络爬虫(Python:Requests、Beautiful Soup笔记)

网络爬虫&#xff08;Python&#xff1a;Requests、Beautiful Soup笔记&#xff09; 网络协议简要介绍一。OSI参考模型二、TCP/IP参考模型对应关系TCP/IP各层实现的协议应用层传输层网络层 HTTP协议HTTP请求HTTP响应HTTP状态码 Requests&#xff08;Python&#xff09;Requests…...

【Kotlin】内联函数

文章目录 内联函数noinline: 避免参数被内联非局部返回使用标签实现Lambda非局部返回为什么要设计noinline crossinline具体化参数类型 Kotlin中的内联函数之所以被设计出来&#xff0c;主要是为了优化Kotlin支持Lambda表达式之后所带来的开销。然而&#xff0c;在Java中我们似…...

Unity技美35——再URP管线环境下,配置post后期效果插件(post processing)

前两年在我的unity文章第10篇写过&#xff0c;后效滤镜的使用&#xff0c;那时候大部分项目用的还是unity的基础管线&#xff0c;stander管线。 但是现在随着unity的发展&#xff0c;大部分项目都用了URO管线&#xff0c;甚至很多PC端用的都是高效果的HDRP管线&#xff0c;这就…...

Redis:持久化RDB和AOF

目录 概述RDB持久化流程指定备份文件的名称指定备份文件存放的目录触发RDB备份redis.conf 其他一些配置rdb的备份和恢复优缺点停止RDB AOF持久化流程AOF启动/修复/恢复AOF同步频率设置rewrite压缩原理触发机制重写流程no-appendfsync-on-rewrite 优缺点 如何选择 概述 Redis是…...

基于python协同过滤推荐算法的音乐推荐与管理系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Python的协同过滤推荐算法的音乐推荐与管理系统是一个集成了音乐推荐和管理的系统&#xff0c;它使用协同过滤算…...

【极客技术】真假GPT-4?微调 Llama 2 以替代 GPT-3.5/4 已然可行!

近日小编在使用最新版GPT-4-Turbo模型&#xff08;主要特点是支持128k输入和知识库截止日期是2023年4月&#xff09;时&#xff0c;发现不同商家提供的模型回复出现不一致的情况&#xff0c;尤其是模型均承认自己知识库达到2023年4月&#xff0c;但当我们细问时&#xff0c;Fak…...

STK Components 二次开发-创建地面站

1.地面站只需要知道地面站的经纬高。 // Define the location of the facility using cartographic coordinates.var location new Cartographic(Trig.DegreesToRadians(-75.596766667), Trig.DegreesToRadians(40.0388333333), 0.0); 2.创建地面站 创建方式和卫星一样生成对…...

数据结构与算法(三)贪心算法(Java)

目录 一、简介1.1 定义1.2 基本步骤1.3 优缺点 二、经典示例2.1 选择排序2.2 背包问题 三、经典反例&#xff1a;找零钱3.1 题目3.2 解答3.3 记忆化搜索实现3.4 动态规划实现 一、简介 1.1 定义 贪心算法&#xff08;Greedy Algorithm&#xff09;&#xff0c;又名贪婪法&…...

057-第三代软件开发-文件监视器

第三代软件开发-文件监视器 文章目录 第三代软件开发-文件监视器项目介绍文件监视器实现原理关于 QFileSystemWatcher实现代码 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&…...

二十七、微服务案例

目录 一、实现输入搜索功能 1、下载代码&#xff0c;在idea上打开 2、新建RequestParams类&#xff0c;用于接收解析请求 3、在启动类中加入客户端地址Bean&#xff0c;以便实现服务 4、编写搜索方法 5、新建返回分页结果类 6、实现搜索方法 7、编写控制类&#xff0c;…...

(C++)string类的模拟实现

愿所有美好如期而遇 前言 我们模拟实现string类不是为了去实现他&#xff0c;而是为了了解他内部成员函数的一些运行原理和时间复杂度&#xff0c;在将来我们使用时能够合理地去使用他们。 为了避免我们模拟实现的string类与全局上的string类冲突(string类也在std命名空间中)&…...

处理数据中的缺失值--删除缺少值的行

两个最主要的处理缺失值的方法是&#xff1a; ❏ 删除缺少值的行&#xff1b; ❏ 填充缺失值&#xff1b; 我们首先将serum_insulin的中的字段值0替换为None&#xff0c;可以看到缺失值的数量为374个&#xff1b; print(pima[serum_insulin].isnull().sum()) pima[serum_insu…...

Kotlin学习——kt里的集合,Map的各种方法之String篇

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…...

MIT 6.824 -- MapReduce Lab

MIT 6.824 -- MapReduce Lab 环境准备实验背景实验要求测试说明流程说明 实验实现GoLand 配置代码实现对象介绍协调器启动工作线程启动Map阶段分配任务执行任务 Reduce 阶段分配任务执行任务 终止阶段 崩溃恢复 注意事项并发安全文件转换golang 知识点 测试 环境准备 从官方gi…...

创新研报|顺应全球数字化,能源企业以“双碳”为目标的转型迫在眉睫

能源行业现状及痛点分析 挑战一&#xff1a;数字感知能力较弱 挑战二&#xff1a;与业务的融合度低 挑战三&#xff1a;决策响应速度滞后 挑战四&#xff1a;价值创造有待提升 挑战五&#xff1a;安全风险如影随形 能源数字化转型定义及架构 能源行业数字化转型体系大体…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合

作者&#xff1a;来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布&#xff0c;Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明&#xff0c;Elastic 作为 …...