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

FreeModbus RTU 移植指南

FreeModbus 简介

FreeModbus 是一个免费的软件协议栈,实现了 Modbus 从机功能:

  • 纯 C 语言
  • 支持 Modbus RTU/ASCII
  • 支持 Modbus TCP

本文介绍 Modbus RTU 移植。

移植环境:

  • 裸机
  • Keil MDK 编译器
  • Cortex-M3 内核芯片(LPC1778/88)

移植概述

1.体系架构相关

项目描述
INLINE宏,编译器相关,内联指令或关键字
PR_BEGIN_EXTERN_C
PR_END_EXTERN_C
宏,按照 C 代码编译
ENTER_CRITICAL_SECTION( )
EXIT_CRITICAL_SECTION( )
宏,进入临界区和退出临界区
BOOL
UCHAR
CHAR
USHORT
SHORT
ULONG
LONG
数据类型
TRUE
FALSE
宏,BOOL 类型变量的值

2.定时器
需要移植的定时器函数

定时器函数描述
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )初始化,由协议栈回调, usTim1Timerout50us 的单位是 50us
void vMBPortTimersEnable( )使能定时器,协议栈回调
定时器计数器清零,然后开始计数
void vMBPortTimersDisable( )禁止定时器,由协议栈回调
定时器计数器清零,停止计数
void prvvTIMERExpiredISR( void )通知协议栈定时器中断发生,需手动安装到定时器中断服务函数中

3.串口
需要移植的函数

定时器函数描述
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )初始化串口硬件,由协议栈回调
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )使能/禁止串口发送和接收,由协议栈回调
BOOL xMBPortSerialPutByte( CHAR ucByte )通过串口发送一字节数据
BOOL xMBPortSerialGetByte( CHAR * pucByte )从串口接收一字节数据
void prvvUARTRxISR( void )通知协议栈串口接收中断发生,协议栈会进行数据接收处理。需手动安装到串口接收中断服务函数中
void prvvUARTTxReadyISR( void )通知协议栈串口发送中断发生,协议栈会进行数据发送。需手动安装到串口发送中断服务函数中

4.事件
事件相关回调函数需要移植:

事件回调函数描述
BOOL xMBPortEventInit( void )初始化
BOOL xMBPortEventPost( eMBEventType eEvent )事件投递
可以在这个函数中解析事件,并执行自己的事件函数。
BOOL xMBPortEventGet( eMBEventType * eEvent )获取事件

mb_config.h 文件属于协议栈的一部分,直接修改不合理
assert,直接调用 C 标准库函数, 但这个依赖硬件

移植细节

并不是所有函数都需要重头编写,协议栈 \freemodbus\demo\BARE\port\ 文件夹下给出了移植框架:

port
|---- port.h :体系架构相关
|---- porttimer.c :定时器相关
|---- portserial.c :串口相关
|---- portevent.c :事件相关

1.体系架构
port.h 文件:

#include <assert.h>
#include <stdint.h>
#include "cmsis_compiler.h"#define	INLINE                      __INLINE
#define PR_BEGIN_EXTERN_C           extern "C" {
#define	PR_END_EXTERN_C             }#ifndef assert
#define assert(ignore) ((void)0)
#endif#define ENTER_CRITICAL_SECTION( )   EnterCriticalSection()
#define EXIT_CRITICAL_SECTION( )    ExitCriticalSection()typedef uint8_t BOOL;typedef unsigned char UCHAR;
typedef char CHAR;typedef uint16_t USHORT;
typedef int16_t SHORT;typedef uint32_t ULONG;
typedef int32_t LONG;#ifndef TRUE
#define TRUE            1
#endif#ifndef FALSE
#define FALSE           0
#endifvoid EnterCriticalSection(void);
void ExitCriticalSection(void);

进入和退出临界区函数,实际上是开关中断,这部分点击这里可以获取详细的信息。我们新建一个 port.c 文件,在这个文件中实现一个可以嵌套使用的进入和退出临界区代码:

#include "cmsis_compiler.h"static uint32_t nesting_count = 0;
static uint32_t old_state;void EnterCriticalSection(void)
{uint32_t cur_state;cur_state = __get_PRIMASK();__disable_irq();if(nesting_count == 0)old_state = cur_state;nesting_count ++;
}void ExitCriticalSection(void)
{nesting_count --;if(0 == nesting_count)__set_PRIMASK(old_state);
}

2.定时器
Modbus RTU 使用超时机制判断数据帧结束:串口超过 3.5 个字符传输时间没有收到数据,则认为一帧结束。
这需要一个硬件定时器。
协议栈会根据传入的波特率自动计算 3.5 个字符传输时间是多少,单位是 50us,简化后的代码如下所示:

/* If baudrate > 19200 then we should use the fixed timer values t35 = 1750us. * Otherwise t35 must be 3.5 times the character time.*/
if( ulBaudRate > 19200 )
{usTimerT35_50us = 35;       /* 1800us. */
}
else
{/* The timer reload value for a character is given by:** ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )*             = 11 * Ticks_per_1s / Baudrate*             = 220000 / Baudrate* The reload for t3.5 is 1.5 times this value and similary* for t3.5.*/usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
}
xMBPortTimersInit( ( USHORT ) usTimerT35_50us )

所以就可以根据传入的 3.5 个字符传输时间 usTimerT35_50us 来初始化硬件定时器。我的系统刚好有个 50us 中断一次的定时器,所以我直接使用这个定时器来移植,移植代码在 porttime.c 文件中:

#include <stdbool.h>
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"static bool IsTimerEnable = false;
static USHORT Timerout50usCount = 0;
static USHORT Timerout50usCountCur = 0;/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{Timerout50usCount = usTim1Timerout50us;IsTimerEnable = false;return TRUE;
}inline void
vMBPortTimersEnable(  )
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */IsTimerEnable = true;Timerout50usCountCur = 0;
}inline void
vMBPortTimersDisable(  )
{/* Disable any pending timers. */IsTimerEnable = false;Timerout50usCountCur = 0;
}/*需手动安装到定时器中断服务函数*/
void
vMBPortTimersISR(  )
{if(IsTimerEnable){Timerout50usCountCur ++;if(Timerout50usCountCur >= Timerout50usCount)prvvTIMERExpiredISR();}
}/* Create an ISR which is called whenever the timer has expired. This function* must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that* the timer has expired.*/
static void prvvTIMERExpiredISR( void )
{( void )pxMBPortCBTimerExpired(  );
}

有一点我很好奇, 3.5 个字符传输时间 usTimerT35_50us 为什么要格式化成 50us 的倍数?
我注意到代码 xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) 在传递参数时进行了一次数据强制转换,也就是协议栈使用的 USHORT 数据类型,一般这个数据类型最大值是 65536,如果不转换成 50us 的倍数,低波特率(比如 1200bps )必然会出现数据溢出现象。
那协议栈为什么又非要使用 USHORT 数据类型呢?
不清楚,大概是当时主流 MCU 还不是 32 位的,USHORT 数据类型可以更快更节省 RAM 。

何时使能定时器?

  1. 启动协议栈(eMBRTUStart
  2. 接收到 1 字节数据(xMBRTUReceiveFSM):复位计数器,重新开始计时

何时关闭定时器?

  1. 停止协议栈(eMBRTUStop
  2. 超时发生(3.5 个字符传输时间):收到新的数据帧,停止计时

定时器与接收关系密切,参与接收状态机的状态迁移:
在这里插入图片描述
3.串口
串口用于收发数据。移植代码在 portserial.c 中:

#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );void down3_set_to_recv(void);
void down3_set_to_send(void);
void down3_put_byte( CHAR data);
void down3_get_byte(CHAR *pucByte);
void init_down3_uart2(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity);/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if(xRxEnable){down3_set_to_recv();}if(xTxEnable){down3_set_to_send();prvvUARTTxReadyISR();}
}BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{init_down3_uart2(ucPORT, ulBaudRate, ucDataBits, eParity);return TRUE;
}BOOL
xMBPortSerialPutByte( CHAR ucByte )
{/* Put a byte in the UARTs transmit buffer. This function is called* by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been* called. */down3_put_byte(ucByte);return TRUE;
}BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{/* Return the byte in the UARTs receive buffer. This function is called* by the protocol stack after pxMBFrameCBByteReceived( ) has been called.*/down3_get_byte(pucByte);return TRUE;
}/*需手动安装到串口接收中断服务函数*/
void
vMBPortSerialRecvISR(void)
{prvvUARTRxISR();
}/*需手动安装到串口发送中断服务函数*/
void
vMBProtSerialSendISR(void)
{prvvUARTTxReadyISR();
}/* Create an interrupt handler for the transmit buffer empty interrupt* (or an equivalent) for your target processor. This function should then* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that* a new character can be sent. The protocol stack will then call * xMBPortSerialPutByte( ) to send the character.*/
static void prvvUARTTxReadyISR( void )
{pxMBFrameCBTransmitterEmpty(  );
}/* Create an interrupt handler for the receive interrupt for your target* processor. This function should then call pxMBFrameCBByteReceived( ). The* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the* character.*/
static void prvvUARTRxISR( void )
{pxMBFrameCBByteReceived(  );
}

4.事件
协议栈使用前后台架构,中断产生 事件 ,主循环处理 事件

事件生产者消费者描述
EV_READY定时器中断 (porttimer.c)
prvvTIMERExpiredISR
主循环 (mb.c)
eMBPoll
协议栈初始化完毕
EV_FRAME_RECEIVED定时器中断 (porttimer.c)
prvvTIMERExpiredISR
主循环 (mb.c)
eMBPoll
接收到一帧数据
如果数据帧校验正确,则产生 EV_EXECUTE 事件
EV_EXECUTE主循环 (mb.c)
eMBPoll
主循环 (mb.c)
eMBPoll
解析命令,生成应答数据,添加 CRC ,启动数据发送,数据将由串口发送中断发送
EV_FRAME_SENT串口发送中断 (portserial.c)
prvvUARTTxReadyISR
主循环 (mb.c)
eMBPoll
应答数据全部发送完成

事件一般用队列实现,以便消费者来不及处理事件时,暂时保存事件。对于简单应用,如果满足消费者消费事件的速度 大于等于 生产者生产事件的速度,则可以使用协议栈 \freemodbus\demo\BARE\port\portevent.c 文件中的源码,直接使用,不用修改:

#include "mb.h"
#include "mbport.h"/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL     xEventInQueue;/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{xEventInQueue = FALSE;return TRUE;
}BOOL
xMBPortEventPost( eMBEventType eEvent )
{xEventInQueue = TRUE;eQueuedEvent = eEvent;return TRUE;
}BOOL
xMBPortEventGet( eMBEventType * eEvent )
{BOOL            xEventHappened = FALSE;if( xEventInQueue ){*eEvent = eQueuedEvent;xEventInQueue = FALSE;xEventHappened = TRUE;}return xEventHappened;
}

在发送事件处就可以完成的功能,为什么要绕一圈非得用事件来完成呢?
方便解耦。
对于裸机环境,使用事件将处理过程从中断转移到主循环,从而使中断服务函数简单。
对于有操作系统的应用,事件可以方便的实现操作系统移植层,实现协议栈进程与中断之间的通讯。协议栈进程会因为等待事件而进入阻塞状态。

相关文章:

FreeModbus RTU 移植指南

FreeModbus 简介 FreeModbus 是一个免费的软件协议栈&#xff0c;实现了 Modbus 从机功能&#xff1a; 纯 C 语言支持 Modbus RTU/ASCII支持 Modbus TCP 本文介绍 Modbus RTU 移植。 移植环境&#xff1a; 裸机Keil MDK 编译器Cortex-M3 内核芯片&#xff08;LPC1778/88&…...

《唐诗三百首》数据源网络下载

2023年的 元宵之夜&#xff0c;这场以“长安”为主题的音乐会火了&#xff01;在抖音&#xff0c;超过2300万人次观看了直播&#xff0c;在线同赏唐诗与交响乐的融合。许多网友惊呼&#xff0c;上学时那些害怕背诵的诗句&#xff0c;原来还可以有这么美的表达这场近80分钟的音乐…...

(深度学习快速入门)第五章第一节2:GAN经典案例之MNIST手写数字生成

获取pdf&#xff1a;密码7281 文章目录一&#xff1a;数据集介绍二&#xff1a;GAN简介&#xff08;1&#xff09;简介&#xff08;2&#xff09;损失函数三&#xff1a;代码编写&#xff08;1&#xff09;参数及数据预处理&#xff08;2&#xff09;生成器与判别器模型&#x…...

雁过留痕,竟是病毒的痕迹?

凌恩生物全新升级宏病毒组分析流程&#xff1b;聚焦DNA&#xff0c;RNA病毒组研究热点&#xff1b;高灵敏度检测vOTUs&#xff1b;多软件整合&#xff0c;精准鉴定病毒序列&#xff1b;直击地化循环关键环节&#xff0c;助力宏病毒组科研成功&#xff01;期刊&#xff1a;Micro…...

Linux基本功系列之sort命令实战

文章目录前言一. sort命令介绍二. 语法格式及常用选项三. 参考案例3.1 按照文本默认排序3.2 忽略相同的行3.3 按数字大小进行排序3.4 检查文件是否已经按照顺序排序3.5 将第3列按照数字大小进行排序3.6 将排序结果输出到文件四. 探讨 -k的高级用法总结前言 大家好&#xff0c;…...

【笔记】移动端自动化:adb调试工具+appium+UIAutomatorViewer

学习源&#xff1a; https://www.bilibili.com/video/BV11p4y197HQ https://blog.csdn.net/weixin_47498728/category_11818905.html 一、移动端测试环境搭建 学习目标 1.能够搭建java 环境 2.能够搭建android 环境 &#xff08;一&#xff09;整体思路 我们的目标是Andr…...

面试复习题--性能检测原理

1、布局性能检测 Systrace&#xff0c;内存优化工具中也用到了 Systrace,这里关注 Systrace 中的 Frames 页面&#xff0c;正常情况下圆点为绿色&#xff0c;当出现黄色或者红色的圆点时&#xff0c;表现出现了丢帧。 Layout Inspector&#xff0c;是 AndroidStudio 自带工具…...

@LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析

背景 最近引入了 Nacos Config 配置管理能力&#xff0c;说起来用法很简单&#xff0c;还是踩了三个坑。 Nacos Config 的 nacos 的帐号密码加密配置后&#xff0c;怎么解密而且在 NacosConfigBootstrapConfiguration 真正注入 Nacos Config 注入之前&#xff0c;而且不能触发…...

2023年个人计划

2023年个人计划 可能是最近太清闲&#xff0c;感觉生活很无聊&#xff0c;就胡乱做下新年的规划吧&#xff0c;扰乱下烦闷的心 1 二宝健健康康&#xff0c;活泼可爱 目前老婆已经怀孕5周左右了&#xff0c;二宝将在进行年中降生&#xff0c;希望老婆少受点罪&#xff0c;二宝…...

加拿大访问学者家属如何办理探亲签证?

由于大多数访问学者的访学期限都为一年&#xff0c;家人来访不仅可以缓解访学的寂寞生活&#xff0c;而且也是家人到加拿大体验国外风情的好机会。家属在国内申请赴加签证时&#xff0c;如果材料齐全&#xff0c;一般上午递交了申请&#xff0c;下午就可以拿到签证。以下是家人…...

操作系统基础---多线程

文章目录操作系统基础---多线程1.为何引入线程程序并发的时空开销线程的设计思路线程的状态和线程控制块TCB2.线程与进程的比较3.线程的实现⭐1.内核支持线程KST2.用户级线程3.组合方式操作系统基础—多线程 1.为何引入线程 利用传统的进程概念和设计方法已经难以设计出适合于…...

2022-12-10青少年软件编程(C语言)等级考试试卷(六级)解析

2022-12-10青少年软件编程&#xff08;C语言&#xff09;等级考试试卷&#xff08;六级&#xff09;解析T1、区间合并 给定 n 个闭区间 [ai; bi]&#xff0c;其中i1,2,...,n。任意两个相邻或相交的闭区间可以合并为一个闭区间。例如&#xff0c;[1;2] 和 [2;3] 可以合并为 [1;3…...

太酷了,用Python实现一个动态条形图!

大家好&#xff0c;我是小F&#xff5e;说起动态条形图&#xff0c;小F之前推荐过两个Python库&#xff0c;比如「Bar Chart Race」、「Pandas_Alive」&#xff0c;都可以实现。今天就给大家再介绍一个新的Python库「pynimate」&#xff0c;一样可以制作动态条形图&#xff0c;…...

单元测试junit+mock

单元测试 是什么&#xff1f; 单元测试&#xff08;unit testing&#xff09;&#xff0c;是指对软件中的最小可测试单元进行检查和验证。至于“单元”的大小或范围&#xff0c;并没有一个明确的标准&#xff0c;“单元”可以是一个方法、类、功能模块或者子系统。 单元测试通…...

2022Q4手机银行新版本聚焦提升客群专属、财富开放平台、智能化能力,活跃用户规模6.91亿人

易观&#xff1a;2022年第4季度&#xff0c;手机银行APP迭代升级加快&#xff0c;手机银行作为零售银行服务及经营的主阵地&#xff0c;与零售银行业务发展的联系日益紧密。迭代升级一方面可以顺应零售银行发展战略及方向&#xff0c;对手机银行业务布局进行针对性调整优化&…...

YOLO-V1~V3经典物体检测算法介绍

大名鼎鼎的YOLO物体检测算法如今已经出现了V8版本&#xff0c;我们先来了解一下它前几代版本都做了什么吧。本篇文章介绍v1-v3&#xff0c;后续会继续更新。一、节深度学习经典检测方法概述1.1 检测任务中阶段的意义我们所学的深度学习经典检测方法 &#xff0c;有些是单阶段的…...

SparkSQL 核心编程

文章目录SparkSQL 核心编程1、新的起点2、SQL 语法1) 读取 json 文件创建 DataFrame2) 对 DataFrame 创建一个临时表3) 通过SQL语句实现查询全表3、DSL 语法1) 创建一个DataFrame2) 查看DataFrame的Schema信息3) 只查看"username"列数据4) 查看"username"列…...

Android核心开发【UI绘制流程解析+原理】

一、UI如何进行具体绘制 UI从数据加载到具体展现的过程&#xff1a; 进程间的启动协作&#xff1a; 二、如何加载到数据 应用从启动到onCreate的过程&#xff1a; Activity生产过程详解&#xff1a; 核心对象 绘制流程源码路径 1、Activity加载ViewRootImpl ActivityThread…...

计算机组成原理第七章笔记记录

仅仅作为笔记记录,B站视频链接&#xff0c;若有错误请指出&#xff0c;谢谢 基本概念 演变过程 I/O系统基本组成 I/O软件 包括驱动程序、用户程序、管理程序、升级补丁等 下面的两种方式是用来实现CPU和I/O设备的信息交换的 I/O指令 CPU指令的一部分,由操作码,命令码,设备…...

ORB-SLAM2编译、安装等问题汇总大全(Ubuntu20.04、eigen3、pangolin0.5、opencv3.4.10)

ORB-SLAM2编译、安装等问题汇总大全&#xff08;Ubuntu20.04、eigen3、pangolin0.5、opencv3.4.10&#xff09; 1&#xff1a;环境说明: 使用的Linux发行版本为Ubuntu 20.04 SLAM2下载地址为&#xff1a;git clone https://github.com/raulmur/ORB_SLAM2.git ORB_SLAM2 2&a…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...