STM32CUBUMX配置RS485 modbus STM32(从机)亲测可用
————————————————————————————————————
⏩ 大家好哇!我是小光,嵌入式爱好者,一个想要成为系统架构师的大三学生。
⏩最近在开发一个STM32H723ZGT6的板子,使用STM32CUBEMX做了很多驱动,包括ADC、UART、RS485、EEPROM(IIC)、FLASH(SPI)、modbus等等。
⏩本篇文章对STM32CUBEMX在RS485通信的基础上做modbus通信做一个详细的使用教程。
⏩感谢你的阅读,不对的地方欢迎指正。
————————————————————————————————————
modbuspoll
- 工具下载
- modbus协议简介
- Modbus协议类型
- Modbus-Rtu协议
- Modbus功能码
- STM32CUBEMX配置
- RS485配置
- 定时器配置
- 驱动代码
- 测试结果
- XCOM串口调试助手作为主机测试
- modbus poll作为主机测试
- 总结
工具下载
Modbus Poll是一个模拟Modbus协议主机的上位机软件,主要用于模拟测试跟其他从机设备通信的过程。与之成套存在的另一个软件–Modbus Slave,则是模拟Modbus协议从机的上位机软件。该软件内部封装标准Modbus协议栈,通过图形化界面使得操作更为简便。目前软件支持01、02、03、04、05、06、15、16功能码,异常报文检测,原始报文查看,数据记录等功能,是调试Modbus协议栈的好帮手。
下载链接:
链接:百度网盘下载链接modbus poll 7.0.1
提取码:lft0
modbus协议简介
参考:
值得收藏 Modbus RTU 协议详解
详解Modbus通信协议—清晰易懂
Modbus协议类型
串行端口存在多个版本的Modbus协议,而最常见的是下面四种:
- Modbus-Rtu 远程终端控制系统 CRC16校验
- Modbus-Ascii Ascii码表示数据 LRC校验
- Modbus-Tcp TCP三种报文类型 无校验
- ModbusPlus
我们这里使用Modbus-Rtu进行编写代码和测试。
Modbus-Rtu协议
Modbus功能码
Modbus规定了多个功能,那么为了方便的使用这些功能,我们给每个功能都设定一个功能码,也就是指代码。
既然搞清楚了原理,那么后面我们开始程序讲解:
STM32CUBEMX配置
RS485配置
参考我之前的文章:
STM32CUBUMX配置RS485(中断接收)–保姆级教程
一定要根据这个文章把RS485调通
定时器配置
首先我们要知道modbus通信的一帧数据是通过每一帧数据之间的间隔时间来确认的。
- 当bps<19200时:超时时间是大于3.5个字节时间。
- 当bps>19200时:超时时间是大于1750us
例如:
bps = 9600: 传输一个字节的时间是1/9600*10 = 10.4ms,3.5个字节时间就是3.5ms
bps = 115200:超时时间就是17500
定时器定时时间计算可以参考我之前的文章:
STM32CUBEMX配置 定时器中断
下面进行定时器配置
我们配置的PSC = 27500-1 ARR = 50-1 TIM3的时钟是275Mhz
所以:定时时间 T = 27500 * 50/275 * 10^6 = 0.00005s = 50us
定时器计数35次,也就是1800 刚好大于1750us ,符合modbus协议
开启定时器中断,优先级需要比串口中断更低
驱动代码
modbus.h
#ifndef MODBUS_H_
#define MODBUS_H_#include "stm32H7xx_hal.h" //HAL库文件声明
#include "gpio.h"
#include "usart.h"#define BUFFER_SIZE 600 //最大数据帧typedef struct {uint8_t myadd;//从机设备地址uint8_t timrun;//定时器uint8_t slave_add;//主机要匹配的从机地址(本设备作为主机时)uint8_t reflag;//接收完成标志位,1:完成 0:未完成uint8_t Host_time_flag;//发送数据标志uint8_t recount;//接收到的字节数unsigned char rcbuf[BUFFER_SIZE];//接受数据帧unsigned char sendbuf[BUFFER_SIZE];//发送数据帧uint32_t timout;//超时时间 单位:msuint32_t Host_Sendtime;//发送完上一帧后的时间计数 单位:ms}MODBUS;// Modbus初始化函数
void Modbus_Init(void);
void Modbus_Event(void);
void Modbus_Func3(void);
void Modbus_Func6(void);
void Modbus_Func16(void);void Modbus_Send_Byte( uint8_t ch );int Modbus_CRC16(uint8_t buff[],int len);
#endif
modbus.c
#include "modbus.h"
MODBUS modbus;
uint16_t Reg[] ={0x0001,0x0012,0x0013,0x0004,0x0025,0x0036,0x0007,0X0008,};//reg是提前定义好的寄存器和寄存器数据,要读取和改写的部分内容// Modbus初始化函数void Modbus_Init(void){modbus.myadd = 0x01; //从机设备地址为2modbus.timrun = 0; //modbus定时器停止计算modbus.slave_add=0x02;//主机要匹配的从机地址(本设备作为主机时)modbus.reflag = 0;//无数据包处理modbus.Host_time_flag = 0;//发送数据标志modbus.recount = 0;//接收到的字节数modbus.timout = 0;//超时时间 单位:msmodbus.Host_Sendtime = 0;//发送完上一帧后的时间计数 单位:ms}// Modbus事件处理函数void Modbus_Event(void){uint16_t crc,rccrc;//crc和接收到的crc//没有收到数据包if(modbus.reflag == 0) //如果接收未完成则返回空{return;}//收到数据包(接收完成)//通过读到的数据帧计算CRC//参数1是数组首地址,参数2是要计算的长度(除了CRC校验位其余全算)crc = Modbus_CRC16(&modbus.rcbuf[0],modbus.recount-2); //获取CRC校验位// 读取数据帧的CRCrccrc = modbus.rcbuf[modbus.recount-2]*256+modbus.rcbuf[modbus.recount-1];//计算读取的CRC校验位//等价于下面这条语句//rccrc=modbus.rcbuf[modbus.recount-1]|(((uint16_t)modbus.rcbuf[modbus.recount-2])<<8);//获取接收到的CRCif(crc == rccrc) //CRC检验成功 开始分析包{ if(modbus.rcbuf[0] == modbus.myadd) // 检查地址是否时自己的地址{switch(modbus.rcbuf[1]) //分析modbus功能码{case 0: break;case 1: break;case 2: break;case 3: Modbus_Func3();break;//这是读取寄存器的数据case 4: break;case 5: break;case 6: Modbus_Func6(); break;//这是写入单个寄存器数据case 7: break;case 8: break;case 9: break;case 16: Modbus_Func16(); break;//写入多个寄存器数据}}else if(modbus.rcbuf[0] == 0) //广播地址不予回应{} } modbus.recount = 0;//接收计数清零modbus.reflag = 0; //接收标志清零}/*********************************************************************************主机:0301 03 00 01 00 01 D5 CA 从地址01开始读读取一个寄存器的数据内容ID 功能码 起始地址 读取寄存器的个数从机返回:01 03 02 00 03 F8 45 返回了两个字节的数据,数据是00 03ID 功能码 几个字节 返回的数据内容*********************************************************************************/// Modbus 3号功能码函数// Modbus 主机读取寄存器值void Modbus_Func3(void){uint16_t Regadd,Reglen,crc;uint8_t i,j; //得到要读取寄存器的首地址Regadd = modbus.rcbuf[2]*256+modbus.rcbuf[3];//读取的首地址//得到要读取寄存器的数据长度Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数//发送回应数据包i = 0;modbus.sendbuf[i++] = modbus.myadd; //ID号:发送本机设备地址modbus.sendbuf[i++] = 0x03; //发送功能码modbus.sendbuf[i++] = ((Reglen*2)%256); //返回字节个数for(j=0;j<Reglen;j++) //返回数据{//reg是提前定义好的16位数组(模仿寄存器)modbus.sendbuf[i++] = Reg[Regadd+j]/256;//高位数据modbus.sendbuf[i++] = Reg[Regadd+j]%256;//低位数据}crc = Modbus_CRC16(modbus.sendbuf,i); //计算要返回数据的CRCmodbus.sendbuf[i++] = crc/256;//校验位高位modbus.sendbuf[i++] = crc%256;//校验位低位//数据包打包完成// 开始返回Modbus数据RS485DIR_TX;//这是开启485发送for(j=0;j<i;j++)//发送数据{Modbus_Send_Byte(modbus.sendbuf[j]); }RS485DIR_RX;//这里是关闭485发送}// Modbus 6号功能码函数// Modbus 主机写入寄存器值void Modbus_Func6() {uint16_t Regadd;//地址16位uint16_t val;//值uint16_t i,crc,j;i=0;Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要修改的地址 val=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //修改后的值(要写入的数据)Reg[Regadd]=val; //修改本设备相应的寄存器//以下为回应主机modbus.sendbuf[i++]=modbus.myadd;//本设备地址modbus.sendbuf[i++]=0x06; //功能码 modbus.sendbuf[i++]=Regadd/256;//写入的地址modbus.sendbuf[i++]=Regadd%256;modbus.sendbuf[i++]=val/256;//写入的数值modbus.sendbuf[i++]=val%256;crc=Modbus_CRC16(modbus.sendbuf,i);//获取crc校验位modbus.sendbuf[i++]=crc/256; //crc校验位加入包中modbus.sendbuf[i++]=crc%256;//数据发送包打包完毕RS485DIR_TX;;//使能485控制端(启动发送) for(j=0;j<i;j++){Modbus_Send_Byte(modbus.sendbuf[j]);}RS485DIR_RX;//失能485控制端(改为接收)}//这是往多个寄存器器中写入数据//功能码0x10指令即十进制16void Modbus_Func16(){uint16_t Regadd;//地址16位uint16_t Reglen;uint16_t i,crc,j;Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //要修改内容的起始地址Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数for(i=0;i<Reglen;i++)//往寄存器中写入数据{//接收数组的第七位开始是数据Reg[Regadd+i]=modbus.rcbuf[7+i*2]*256+modbus.rcbuf[8+i*2];//对寄存器一次写入数据}//写入数据完毕,接下来需要进行打包回复数据了//以下为回应主机内容//内容=接收数组的前6位+两位的校验位modbus.sendbuf[0]=modbus.rcbuf[0];//本设备地址modbus.sendbuf[1]=modbus.rcbuf[1]; //功能码 modbus.sendbuf[2]=modbus.rcbuf[2];//写入的地址modbus.sendbuf[3]=modbus.rcbuf[3];modbus.sendbuf[4]=modbus.rcbuf[4];modbus.sendbuf[5]=modbus.rcbuf[5];crc=Modbus_CRC16(modbus.sendbuf,6);//获取crc校验位modbus.sendbuf[6]=crc/256; //crc校验位加入包中modbus.sendbuf[7]=crc%256;//数据发送包打包完毕RS485DIR_TX;;//使能485控制端(启动发送) for(j=0;j<8;j++){Modbus_Send_Byte(modbus.sendbuf[j]);}RS485DIR_RX;//失能485控制端(改为接收)}void Modbus_Send_Byte( uint8_t ch ){/* 发送一个字节数据到USART2 */HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xff); //while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET); //等待发送结束}int Modbus_CRC16(uint8_t buff[],int len)
{unsigned short tmp = 0xffff;unsigned short ret1 = 0;for(int n = 0; n < len; n++) //此处的6 -- 要校验的位数为6个{tmp = buff[n] ^ tmp;for(int i = 0;i < 8;i++) //此处的8 -- 指每一个char类型又8bit,每bit都要处理{if(tmp & 0x01){tmp = tmp >> 1;tmp = tmp ^ 0xA001;}else{tmp = tmp >> 1;}}}ret1 = tmp >> 8; //将CRC校验的高低位对换位置ret1 = ret1 | (tmp << 8);return ret1;
}
目前只写了03(读寄存器)、06(写一个寄存器)、16(写多个寄存器)三个功能
stm32_h7xx_it.c
extern MODBUS modbus;void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 *//* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 */if( modbus.reflag==1) //有数据包正在处理{return ;} modbus.rcbuf[modbus.recount++] = USART1_aRxBuffer[0];modbus.timout = 0;if(modbus.recount == 1) //已经收到了第二个字符数据{modbus.timrun = 1; //开启modbus定时器计时}HAL_UART_Receive_IT(&huart1, (uint8_t *)USART1_aRxBuffer,1); //添加的一行代码/* USER CODE END USART1_IRQn 1 *//* USER CODE END USART1_IRQn 1 */
}/******************************************************************************* @ 函数名 : HAL_TIM_PeriodElapsedCallback* @ 功 能 : 定时器超时中断回调函数* @ 参 数 : htim 定时器名 * @ 返回值 : 无******************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM3) {if(modbus.timrun != 0)//运行时间!=0表明{modbus.timout++;if(modbus.timout >=35)//大于1750us{modbus.timrun = 0;modbus.reflag = 1;//接收数据完毕modbus.timout = 0;USART1_RX_STA|=0x8000;}}modbus.Host_Sendtime++;//发送完上一帧后的时间计数if(modbus.Host_Sendtime>1000)//距离发送上一帧数据1s了{//1s时间到modbus.Host_time_flag=1;//发送数据标志位置1}}
}
上面写了RS485串口中断处理函数和定时器中断处理函数
main.c
HAL_TIM_Base_Start_IT(&htim3); //启动定时器TIM3Modbus_Init();//本机作为从机使用时初始化RS485DIR_RX;//拉低PB5,更改RS485模式为接收while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */Modbus_Event();//本机作为从机使用时}/* USER CODE END 3 */
}
主函数只需要写一个初始化,在循环中调用Modbus_Event()函数循环查询就可以了。
测试结果
XCOM串口调试助手作为主机测试
主机发送解析:
01 03 00 00 00 04 44 09
01:从机地址
03:功能码,读寄存器
00 00:读的起始地址
00 04:要读的数据的个数
44 09:校验码
从机返回解析:
==01 03 08 00 01 00 12 00 13 00 04 CD 12 ==
01:从机地址
03:功能码,读寄存器
08:数据的位数,8个字节
00 01 00 12 00 13 00 04:接收到四个数据:0001,0012,0013,0004
CD 12:校验码
校验方式是CRC16
我们可以计算出来,从机返回的数据是没有问题的,然后我们后面使用modbus poll作为主机进行测试:
modbus poll作为主机测试
1.打开modbus poll,Setup->read/write defination:
2.Connection->connect
3.查看结果
开始显示的不是16进制,可以选中这些数据 Display->Hex - Ascall,这样就是16进制显示了,可以看到读取了我们程序中写的数据。
点击这个可以查看发送和返回的数据包
可以看到和我们刚才串口调试的是一样的结果。
总结
本次实验,我们在RS485通信的基础上实现了modbus-RTU协议,当然只写了03、06、16功能码,测试都是没有问题的,图方便我只放上了03的测试,你们可以把这个都测试一遍,甚至可以把他的功能写全面。
相关文章:

STM32CUBUMX配置RS485 modbus STM32(从机)亲测可用
———————————————————————————————————— ⏩ 大家好哇!我是小光,嵌入式爱好者,一个想要成为系统架构师的大三学生。 ⏩最近在开发一个STM32H723ZGT6的板子,使用STM32CUBEMX做了很多驱动&#x…...
系统设计类题目汇总
1 设计一个系统统计当前时刻北京用户在线人数 【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态) 1.1 方案一: 在用户登录时,使用 Redis SET 将用户 ID 添加到一个特定的键(例如 “online:beijing”)。用户退出时&…...

css滚动条样式指南
css滚动条样式指南 滚动条是网页设计中经常被忽视的元素。虽然它看起来像是一个小细节,但它在网站导航中起着至关重要的作用。默认的滚动条可能看起来不合适,有损整体美观。本文将介绍如何使用 CSS 自定义滚动条。 在 Chrome、Edge 和 Safari 中设置滚…...
vue子组件修改父组件传递的变量(自定义日期时间组件,时间间隔为15分钟或者一个小时)
vue子组件修改父组件传递的变量 子组件不能直接修改父组件变量的值,但是可以通过调用父组件的方法来修改。 实现步骤 在父组件声明变量 export default {data() {return {startTime:"",......},......} }在父组件使用子组件并传递数据,修改…...
【PyTorch】nn.Conv2d函数详解
nn.Conv2d 是 PyTorch 中的一个卷积层,用于实现二维卷积操作 torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_modezeros, deviceNone, dtypeNone )参数解释 in_channels:输入的通…...

数智保险 创新未来 | GBASE南大通用亮相中国保险科技应用高峰论坛
本届峰会以“数智保险 创新未来”为主题,GBASE南大通用携新一代创新数据库产品及金融信创解决方案精彩亮相,与国内八百多位保险公司高管和众多保险科技公司技术专家,就保险领域数字化的创新应用及生态建设、新一代技术突破及发展机遇、前沿科…...
分布式天梯图算法在 Redis 图数据库中的应用
分布式天梯图算法在 Redis 图数据库中的应用 一、简介1 天梯图算法2 天梯图算法在Redis的应用 二、Redis分布式天梯图算法设计与优化1 基于天梯图的分布式算法设计2 多节点扩展与负载均衡优化3 数据存储方案与压缩策略 三、技术实现3.1 系统架构设计3.2 技术选型3.3 关键实现细…...

观察者模式——对象间的联动
1、简介 1.1、概述 在软件系统中,有些对象之间也存在类似交通信号灯和汽车之间的关系。一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动,正所谓“触一而牵百发”。为了更好地描述对象之间存在的这种一…...

【雕爷学编程】Arduino动手做(189)---特别苗条,使用微波传感器控制的纤细小车
装修屋子,找了一段墙面布线槽,外槽宽度只有23毫米,截取一段长为24厘米,尝试做个苗条小车 先在线槽上安装了二只N20小电机 装上二个快餐盒盖做轮子 测试一下使用3.7V锂电池的动力系统(视频) https://v.youk…...
机器学习基础算法及其实现
线性回归 知识点: 1. 线性回归模型可以使用不同的目标函数,最常用的是最小二乘法、最小绝对值法和最大似然法。 2. 在最小二乘法中,目标是最小化实际值与预测值之间的误差平方和,这可以通过求导数等方法来求解。 3. 在最小绝对值…...

docker安装MinIO
简介 Minio 是一个面向对象的简单高性能存储服务。使用 Go 语言编写,性能高、具有跨平台性。 Minio 官网为:https://min.io ,有一个中文站点,单内容更新不是很及时,建议从原始官网学习。 本文采用 Docker 安装&…...

第5章 运算符、表达式和语句
本章介绍以下内容: 关键字:while、typedef 运算符:、-、*、/、%、、--、(类型名) C语言的各种运算符,包括用于普通数学运算的运算符 运算符优先级以及语句、表达式的含义 while循环 复合语句、自动类型转换和强制类型转换 如何编写…...

24考研数据结构-图的存储结构邻接矩阵
目录 6.3 储存结构(邻接表表示法)1. 储存方式2. 结构3. 图的邻接表存储表示(算法)4. 结论5. 邻接矩阵和邻接表的对比邻接矩阵优点:缺点: 邻接表优点:缺点: 邻接矩阵与邻接表的关系 6…...

在线推算两个日期相差天数的计算器
具体请前往:在线推算两个日期相差天数的计算器...

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
Spring源码系列文章 Spring源码解析(一):环境搭建 Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三):bean容器的刷新 Spring源码解析(四):单例bean的创建流程 Spring源码解析(五)&…...

【C#学习笔记】引用类型(1)
文章目录 引用类型class匿名类 记录引用相等和值相等record声明 接口delegate 委托合并委托/多路广播委托 引用类型 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对…...

STM32CubeMX+VSCODE+EIDE+RT-THREAD 工程创建
Eide环境搭建暂且不表,后续补充。主要记录下Vscode环境下 创建Rt-thread工程的过程。分别介绍STM32CubeMX添加rtt支持包的方式和手动添加rtt kernel方式。STM32CubeMX生成工程的时候有"坑",防止下次忘记,方便渡一下有缘人ÿ…...

java中javamail发送带附件的邮件实现方法
java中javamail发送带附件的邮件实现方法 本文实例讲述了java中javamail发送带附件的邮件实现方法。分享给大家供大家参考。具体分析如下: JavaMail,顾名思义,提供给开发者处理电子邮件相关的编程接口。它是Sun发布的用来处理email的API。它…...
Stable Diffusion高阶技能(2)-稳定扩散百态:解密AI绘画工具「SD WebUI」的提示词高级使用策略
简介 在我们的生活中,艺术元素可谓无处不在,而处于中心地位的绘画,无疑是携带着强烈的艺术魅力。现如今随着AI技术的日新月异,AI绘画对我们的生活世界的改造影响越来越深远。那么,如何让我们在AI绘画工具中更好的指导AI完成我们心中的作品呢? 这需要我们玩转这个工具的…...
【果树农药喷洒机器人】Part2:机器人变量喷药系统硬件选型
本专栏介绍:付费专栏,持续更新机器人实战项目,欢迎各位订阅关注。 关注我,带你了解更多关于机器人、嵌入式、人工智能等方面的优质文章! 文章目录 一、引言二、变量喷药系统总体要求2.1系统功能要求2.2系统技术要求三、机器人关键硬件选型3.1深度相机概述与选型3.2单片机选…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...