BetaFlight模块设计之三十六:SoftSerial

news2024/11/27 8:24:01

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资源紧张时。

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

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

相关文章

盘点43个Android项目源码安卓爱好者不容错过

盘点43个Android项目源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1yHmkUeX4vxVag9Yr0yeQRg?pwd8888 提取码&#xff1a;8888 项目名称 Android NDK直播项…

单片机学习5——外部中断程序

#include<reg52.h>unsigned char a; sbit lcden P3^4;void main() {lcden0;EA1;EX01;IT00;a0xF0; //点亮4位小灯while(1){P1a;} }//中断服务程序 void ext0() interrupt 0 // 0 表示的是外部中断源0 {a0x0f; // 中断处理完&#xff0c;再返回主…

2.5 - 网络协议 - HTTP协议工作原理,报文格式,抓包实战

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 HTTP协议 1、HTTP协议工作原理2、HTTP协议报文3、H…

NX二次开发UF_CURVE_ask_offset_direction_2 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_offset_direction_2 Defined in: uf_curve.h int UF_CURVE_ask_offset_direction_2(UF_STRING_p_t input_curves, double offset_direction_vector [ 3 ] , double dra…

如何使用OpenCV转换图像并创建视频,实现Ken Burns特效

一、Ken Burns特效 当使用OpenCV时,最常使用的是图像,但是我们也可以多个图像创建动画,通过引入时间轴更容易可视化。 Ken Burns特效这是一种以电影制片人肯伯恩斯 (Ken Burns) 命名的平移和缩放技术,Ken Burns 效果不是在屏幕上显示大型静态照片,而是裁剪细节,然后平移图…

美食网站基本结构

代码&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>美食网站首页</title> <link rel"stylesheet" href"https://cdn.staticfile.org/layui/2.5.6/css/layui.min.c…

博物馆线上导览系统的设计与实现-计算机毕业设计源码64574

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

Couchdb 权限绕过漏洞复现(CVE-2017-12635)

Couchdb 权限绕过漏洞复现&#xff08;CVE-2017-12635&#xff09; ​​ 开启环境给了三个端口号&#xff0c;不知道哪个是正常的&#xff0c;最后试出来52226端口正常。 登录URL&#xff1a;http://192.168.91.129/_utils/# 来到了登录页面 ​​ 用postman发送PUT方法的…

极客时间:使用本地小型语言模型运行网页浏览器应用程序。

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

你好python!——python中的函数与数据容器

一、函数的定义 1.1函数定义语法 1.2函数的参数 1.2.1参数的传入 python中函数的参数和C语言函数的参数其实一样&#xff0c;他们都是形参&#xff0c;是实参的一份临时拷贝。我们来定义一个加法函数来看看函数的参数传入&#xff1a; 参数之间使用逗号进行分隔。 1.2.2函…

在线知识库管理平台zyplayer-doc

什么是 zyplayer-doc &#xff1f; zyplayer-doc 是一款适合团队和个人私有化部署使用的知识库、笔记、WIKI 文档管理工具&#xff0c;同时还包含数据库管理、Api 接口管理等模块。 个人用户可免费下载部署使用&#xff0c;部署后也可以购买商业授权进行激活。 安装 建数据库…

elasticsearch 实战

文章目录 项目介绍导入项目 Elasticsearch Java API 查询文档快速入门发起查询请求解析响应完整代码 match查询精确查询布尔查询排序、分页高亮高亮请求构建高亮结果解析 项目介绍 本项目是一个由spring boot 3.0.2在gradle 8.4和java 21的环境下搭建的elasticsearch项目demo&…

机器学习的复习笔记3-回归的细谈

一、回归的细分 机器学习中的回归问题是一种用于预测连续型输出变量的任务。回归问题的类型和特点如下&#xff1a; 总之&#xff0c;支持向量回归是一种用于解决回归问题的机器学习方法&#xff0c;通过寻找支持向量和引入容忍度范围来建立回归模型&#xff0c;能够处理非线性…

【腾讯云 HAI域探秘】基于高性能应用服务器HAI部署的 ChatGLM2-6B模型,我开发了AI办公助手,公司行政小姐姐用了都说好!

目录 前言 一、腾讯云HAI介绍&#xff1a; 1、即插即用 轻松上手 2、横向对比 青出于蓝 3、多种高性能应用部署场景 二、腾讯云HAI一键部署并使用ChatGLM2-6B快速实现开发者所需的相关API服务 1、登录 高性能应用服务 HAI 控制台 2、点击 新建 选择 AI模型&#xff0c;…

【GD32307E-START】RT-Thread移植测试

【GD32307E-START】RT-Thread移植测试 1. 软硬件平台 GD32F307E-START Board开发板MDK-ARM KeilGCC Makefile 2. 物联网RTOS—RT-Thread RT-Thread RT-Thread诞生于2006年&#xff0c;是一款以开源、中立、社区化发展起来的物联网操作系统。 RT-Thread主要采用 C 语言编写…

RH2288H V3服务器使用ISO安装系统

1.配置和服务器相同网段地址&#xff0c;RH2288H V3服务器bmc管理网口默认IP是192.168.2.100/24&#xff0c;默认用户root&#xff0c;默认Huawei12#$&#xff0c;网线连接BMC口&#xff0c;登录。默认密码可以在开机时按del键进入配置页面修改 2.配置raid&#xff0c;生产环境…

SpringCloud 微服务全栈体系(十八)

第十一章 分布式搜索引擎 elasticsearch 八、RestClient 查询文档 文档的查询同样适用 RestHighLevelClient 对象&#xff0c;基本步骤包括&#xff1a; 准备 Request 对象准备请求参数发起请求解析响应 1. 快速入门 以 match_all 查询为例 1.1 发起查询请求 代码解读&…

基于Java SSM框架+Vue实现药品销售进销存网站项目【项目源码+论文说明】

基于java的SSM框架Vue实现药品销售进销存网站演示 摘要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于药品管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了…

岂曰无衣 汉家衣裳再现锦绣美景

——福州第五届1122汉服节出行盛大开幕11月25日下午&#xff0c;闽江之心海丝广场&#xff0c;一场盛大的汉服文化节——福州第五届1122汉服节出行活动在这里隆重开幕。这个被誉为“穿在身上的历史”的传统文化&#xff0c;在这片古老而神秘的土地上焕发出新的生机与活力。据了…

Win10电脑用U盘重装系统的步骤

在Win10电脑中&#xff0c;用户遇到了无法解决的系统问题&#xff0c;用户这时候就可以考虑重装Win10系统&#xff0c;这样即可轻松解决问题&#xff0c;从而满足自己的操作需求。接下来小编给大家详细介绍关于Win10电脑中用U盘重装系统的教程步骤。 准备工作 1. 一台正常联网可…