OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1734|回复: 29

我的C代码学习分享,微信公众号持续分享中

[复制链接]

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
发表于 2024-7-25 09:05:04 | 显示全部楼层 |阅读模式
本帖最后由 szczyb1314 于 2024-11-13 18:01 编辑

电压区间段监控,用来处理系统中存在多个电压区间段,并且每个区间段之间都有滞回区间的情形。比如在某个应用中,存在多个区间段(比如有区间1/区间2/区间3/区间4/区间5/区间6且每个区间段之间都有滞回区间)需要监控并实时的得出当前稳定的电压区间段的值。扩展开来,对某些应用,也可以将多个电压区间段合并使用,这时候只需要增加一个中间层的适配模块来将电压区间段监控的结果变换为应用需要的区间段或状态,比如将区间段126变换为停止工作状态,区间段35变换为工作但数据不可靠状态,区间段4变换为工作且数据可靠状态;或者将区间段12356变换为无数据状态,区间段4变换为有数据状态。反正只要电压区间段监控模块提供消抖后的稳定的区间段值,那么应用层就可根据这个区间段值做任何应用逻辑而不用操心底层实际的电压是咋变化的。
电压区间段监控
电压状态的监控,电压所处的区间有欠压
\滞回\正常\滞回\过压这几个区间。一般情形:从过/欠压区间进入正常区间时均会经过滞回区间,所以在滞回区间锁定消抖计数器的值到消抖的最大次数,进入正常区间后再增加消抖计数器的值。同样的,从正常区间进入过/欠压区间时也均会经过滞回区间,所以在滞回区间锁定消抖计数器的值到消抖的最大次数,进入过/欠压区间后再减小消抖计数器的值。特殊情形:电压变化非常的迅速,从过/欠压区间直接进入正常区间并未经过滞回区间,这时先将消抖计数器复位到消抖的最大次数然后开始增加消抖计数器的值;同样的,从正常区间直接进入过欠/压区间并未经过滞回区间,这时先将消抖计数器复位到消抖的最大次数然后开始减小消抖计数器的值;电压从过/欠压区间直接进入欠/过压区间并未经过滞回和正常区间,这时消抖计数器保持在0,电压状态直接切换。等消抖计数器的值减小到0或增大到消抖最大值的2倍,则认为消抖完成,得到电压监控的稳定状态。
电压状态监控
一个用PyQt5设计模仿的Tester,UDS诊断时的Client。由于缺少CANoe,license,当只有PCAN时,无法对ECU进行UDS诊断,PCAN需要写脚本实现收发多帧报文和流控报文,所以需要设计一个GUI+Python驱动PCAN来实现基本的UDS诊断的多帧报文和流控报文的收发。另外UDS也可以基于最普通的Uart实现,在没有CAN接口的MCU上实现UDS诊断,所以需要设计一个GUI+Python驱动Serial来实现基本的UDS诊断的多帧报文和流控报文的收发来作为Client去测试ECU。所以Uart_Tester是一个我自己设计的GUI,利用Python驱动PCAN或者Serial实现的可充当简易的UDS诊断的Client上位机。
PyQt5_2- 我的Tester之Uart_Tester (qq.com)
一个用
PyQt5实现的简单的数据变换的工具箱,因为在用CANoe或者PCAN测试UDS诊断时,CAN/LIN总线上收发的数据总是Hex/Dec格式显示,时不时的需要进行格式转换(Hex<==>DecHex<==>Bin Ascii <==>Hex   Float<==>Hex),否则观察起来不是很友好,所以有了做一个数据变换工具箱的想法。
PyQt5_1- ToolsBox 之 数据变换工具箱(qq.com)

一个我曾经使用过的将“模拟量”离散化的简单方法
–Range Index。这里的“模拟量”不是指真实环境中的真实模拟量(比如真实的电压、真实的温度等),这些真实的模拟量需要通过MCUADC外设或者外挂的ADC芯片进行采样才能得到数字量;我这里的“模拟量”是代码中代表某个东西的变量值,它的范围连续,看起来就像个模拟量一样,只是我们想把它离散化到[Dmin,Dmax)。已知“模拟量”的最大值Amax,最小值Amin,离散化后的“数字量”最大值Dmax,最小值Dmin,假若当前的“模拟量”值为Val,那么离散化后的“数字量”的值为(Dmax-Dmin)*(Val-Amin)/(Amax-Amin)+Dmin。离散化后配合其他逻辑可快速实现对“模拟量”范围调整的需求。
杂项1 - RangeIndex (qq.com)

FSM
有限状态机:其要素有状态、事件、执行的动作、转移的下一个状态。当建立状态转移表后,其实现方式有两种,一种是在当前的状态中判断已发生的事件,另一种是在已发生的事件中判断当前的状态。两种实现方式实现的功能完全相同,但是在状态中判断事件时由于代码的写法(if-else中判断那个事件发生写在前面)的原因导致事件有了隐含的优先级,其实事件本身是随机发生的,并没有优先级之说;而在事件中判断判断状态,由于事件发生时的状态是确定且唯一的,根据其执行动作并进行状态的迁移,思路清晰,简洁高效。
FSM有限状态机 (qq.com)

MCU
应用中最常见按键消抖,即读取到连续多次按下/释放才认为是按下/释放;MCU监控供电电压的状态,如连续多次采样到过压才认为过压;CAN/LIN通信中连续几帧收到某个信号的值为x才认为这个信号为x。这其中都有消抖的概念:即连续多次发生或出现才确认。思维发散开来,凡是能将某个“东西”转换为离散的值,那么就可以用多次出现/发生才确认的思路来进行消抖。这里的“东西”可以是机械按键输出、矩阵按键的输出、电压、通信总线信号、传感器的数字/模拟输出,只要能将它们转换为可消抖的离散值,那么就可以对这个“东西”进行消抖处理。今天就分享一个可以应对多种情形的通用消抖模块。
通用的消抖器 (qq.com)

前面一篇分享了我在编码实践过程中积累下来的几个用宏定义实现的位操作方法,我可以使用它们快速的实现对任意普通类型
(构造类型除外)变量的位操作。今天这一篇分享我曾经学习C编程的表驱动法时写着玩的可控制舍入level的分数除法(Fraction divider)。一般我们对小数都是四舍五入处理的,其实在有些情形下我们也是可以根据需要实现其他类型的舍入控制的,比如二舍三入,八舍九入等。
分数除法 (qq.com)

前面两篇分享了一个用
Python设计实现的处理UDS诊断响应数据到喇叭的DTC或者故障状态的小工具,这个工具有两种使用场景,一种是帮助我解析“19022FUDS诊断请求得到的响应数据到喇叭的DTC信息;另一种是帮我解析“3103600CUDS诊断请求得到的响应数据到喇叭当前的故障状态(FaultStatus)信息。
今天这一篇分享我使用C语言过程中积累下来的几个用宏定义实现的位操作方法,我可以用它们快速的实现对任意普通类型(非结构体)变量的位操作。
C语言中常见的IO操作宏定义 (qq.com)

前一篇分享了
Python实现的输出带颜色log信息的模块,这个模块可输出:
1)
当前的系统时间,比如2024-07-2810:41:21:207
2)
输出log信息的模块ID,比如OS/NVM/ECUM/ASYS/SLOG/DURT
3)
当前这条loglevel,比如FATAL/ERROR/WARNING/INFO/DEBUG
4)
可根据loglevel,将log以不同的颜色区分
5)
当前这条log输出的信息字符串,比如”kernel panic”
今天分享前面提到的应用到CANoe测试中的处理诊断响应数据到喇叭故障信息的工具实现:DiagResp2SpkDtcInfo.py,这个工具处理诊断响应数据,将其转换为24个喇叭的DTC信息或者故障信息,方便我测试DTC,验证开发的ECU代码。
Python分享2 - 处理UDS诊断响应到喇叭故障信息(qq.com)
常见的,在一个成熟的系统中输出log信息时,一般log信息会包括:
1)
当前的系统时间,比如2024-07-2810:41:21:207
2)
输出log信息的模块ID,比如OS/NVM/ECUM/ASYS/SLOG/DURT
3)
当前这条loglevel,比如FATAL/ERROR/WARNING/INFO/DEBUG
4)
可根据loglevel,将log以不同的颜色区分
5)
当前这条log输出的信息字符串,比如”kernel panic”
前面一篇分享的CANoe测试UDS诊断Panel内容的最后,提到了一个Python实现的工具,帮助我处理诊断响应数据中的DTC信息到喇叭的对应信息。今天就先分享这个工具中我自己实现的一个可以输出带颜色的log信息模块,这个模块帮助我在输出信息时按照成熟的log系统一样,输出带颜色,带时间,带调用者信息,带log信息等级的log,帮助我在开发Python代码时调试、查看、分析更方便快捷。
Python分享1 - 输出带颜色的log信息(qq.com)
今天分享我在CANoe中测试UDS 诊断的Panel,在PanelCapl脚本中应用了前面分享的那几个函数:Clib_Strstr()Clib_Hex2Str()Clib_Str2Hex()。这几个函数本来就是在写Capl是有需求才设计的,后来在C环境中测试验证成功,现在在Capl脚本中测试验证。用CaplPanel来测试UDS诊断,主要是因为我们用Arxml开发的诊断,不是用CDD文件来开发,所以在CANoe中测试UDS诊断就有点麻烦(如果有CDD文件则导入CANoe,测试UDS诊断会方便很多,但是我们只有Arxml没有CDD,对我软件开发过程中想要简单测试测试UDS诊断开发是否正确时就很尴尬,我不好处理多帧及流控帧的情况,只能写Capl)
CANoe测试Panel - 应用前面的几个函数 (qq.com)
今天分享我在CANoe中做测试UDS诊断的Panel和写测试Capl脚本时自己写的两个函数(Clib_Hex2Str()Clib_Str2Hex())
为什么出这两个函数?因为我有这样的两个应用场景:
1)
CANoe测试PanelInput box中输入hex形式的UDS诊断请求,在Capl脚本转换为byte型数组并发送给ECU,那么需要Clib_Str2Hex()函数来实现hex形式字符串到byte型数组的变换;比如在panel中输入“19022F”,Capl脚本需要转换为byte型数组{0x19,0x02,0x2F},然后按照TP层分包协议转为一个单帧CAN报文[0x03,0x19,0x02,0x2F,0xAA, 0xAA,0xAA, 0xAA]并发送;
2) UDS
诊断服务响应数据在ECUTP层被分包发送后,CANoe 中的Capl脚本按照TP层协议完成组包接收,这时的诊断响应数据是在一个byte型数组中的,这时我想把诊断响应数据以一个hex形式字符串显示到CANoe测试panel的中,那么需要Clib_Hex2Str()函数来实现byte型数组到hex形式字符串的转换;比如我接受到的诊断响应CAN报文为
[0x10,0x0F,0x59,0x02,0xFF,0x9A,0x01,0x11] //
首帧
[0x21,0x27,0xD1,0x4B,0x51,0x2F,0xD1,0x4C] //
续帧
[0x22,0x51,0x2F,0x00,0x00,0x00,0x00,0x00] //
续帧
Capl
脚本按照TP层协议收包完成后的响应数据为{0x59,0x02,0xFF,0x9A,0x01,0x11,0x27,0xD1,0x4B,0x51,0x2F,0xD1,0x4C, 0x51,0x2F },使用Clib_Hex2Str()函数来转换为hex形式的字符串,那么paneloutput box中应该借助环境变量显示字符串“5902FF9A011127D14B512FD14C512F”。
UDS测试Capl脚本中常用的两个函数 (qq.com)
都用过C库中的strstr()函数吧?它的功能是:在母字符串中查找第一次出现子字符串的“位置”。例如从“ABCDEFG”中查找“DE”第一次出现的位置。是不是跟我前面分享过的查找系列有点像,但是它又不是。前面分享的查找是从数组中查找某一个元素,这里却是从大数组中查找某个小数组。注意strstr()的局限是只能用在字符串中(字符串就是字符数组),却不能用在其他类型的数组,比如从int16_tuint32_t、结构体类型的大数组中查找同类型的小数组是否存在。来看看我扩展的可用于任意类型的Clib_Strstr()函数吧,从任意类型的大数组中查找同类型的小数组是否存在,这里的数组类型可以是uint8_t/int8_t/ uint16_t/ int16_t/ uint32_t/ int32_t/ 自定义的结构体类型等。
Clib_Strstr代替受限的strstr (qq.com)
线性插值,在数值分析中也是比较重要的一节。任何模拟量信号在X-Y坐标系中均是由无数个点(x,y)连成的一条曲线。在嵌入式MCU中使用某个模拟量信号,一是用数学函数,但是实际信号的函数表达式非常复杂,MCU计算这条曲线上的点比较复杂且耗时;二是查表法,一般都是提前计算好这条曲线上的点并存储在数组中作为一个表,使用时靠查表得到结果;三就是今天提到的线性插值法,一条曲线在X-Y坐标中经无数次分段后,就可以看作是由无数段非常短的直线拼接而来,只需要标定这几条直线的连接端点形成一个表,而这些短直线之间的点则可以用线性插值法来快速计算(y=(Y1-Y0)/(X1-X0)*(x-X0)+Y0)。这样子,即解决了查表法中表格太大的问题,且比较近似的还原了这条实际曲线;同时还解决了直接用数学函数计算过于复杂耗时的问题。
插值计算 - 线性插值 (qq.com)
一个系统设计中最核心的东西是什么?应该是数据的组织,访问,存储方式。我理解一个系统的主要功能可以看作是对数据处理的功能,数据驱动了系统的运行。而软件只是人们借助某种语言尝试固化下来的一段自己的思维。有了“数据是易变的,而逻辑是稳定的”这样子的认知,表驱动法则是践行代码设计时保持核心+配置分开方法的最佳选择,表驱动法天生注重数据的组织,访问和存储。都说,程序=算法+数据结构,其实应该是:程序=算法(逻辑)+数据(结构)。那么到底是算法(逻辑)重要还是数据(结构)重要呢?就我自己的理解是数据(结构)要比算法(逻辑)要重要。你有牛逼的算法(逻辑),但是你的待处理的数据(结构)组织的一塌糊涂,再牛逼的算法(逻辑)它的性能也会变差,同时由于你数据结构组织的不好导致算法(逻辑)反而变得复杂;再假如你的待处理的数据组织的仅仅有条,那么你的逻辑简答了,算法明了了,性能也会上去。
代码设计时考虑数据(结构)优先(qq.com)
所有的设计模式均是为扩展(新增)/维护(变更)而生,但是会牺牲部分实时性,但是随着现在MCU的发展(主频越来越快,内存越来越多),算力越来越强,这点牺牲也就不是那么重要了。为了方便扩展(新增)/维护(变更),代码设计均是core + configuration的结构,即核心代码逻辑/算法 + 数据配置表的方式。
代码设计时的扩展性和维护性的考虑 (qq.com)
LRC
BCC校验方法,LRC常见在hex文件中对每一条记录的校验,BCC校验常见在简单串口通信协议中。LRCBCC跟前面的CRC8/CRC16/CRC32一起通用化处理一下形成一个校验模块,对外提供统一的调用接口方便Application调用,通过简单配置即可快速实现任意类型的CRC8/CRC16/CRC32,还可自创自己的CRC8/CRC16/CRC32
校验 - LRC & BCC (qq.com)
一个通用化的CRC32校验模块,即我可以通过配置实现任意类型的CRC32校验,这里可以是大家熟知的经典的CRC32,比如CRC32-MPEG-2CRC32,也可以通过配置自创一种CRC32,自创的CRC32可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC32,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC32校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人想要破解你的CRC32校验,总是会从经典的CRC32开始尝试,哪会想到你丫居然自定义CRC32校验。
校验 - CRC32 (qq.com)
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc
搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C
语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C
语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-7-25 19:30:09 | 显示全部楼层
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc
搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType ()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C
语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C
语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-7-27 12:54:56 | 显示全部楼层
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)
啦啦啦
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-7-29 09:34:30 | 显示全部楼层
顶顶顶顶顶顶顶顶
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-7-30 10:02:57 | 显示全部楼层
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)
啦啦啦
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-7-31 09:02:02 | 显示全部楼层
顶顶,支持支持
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-1 09:20:32 | 显示全部楼层
一个通用化的CRC32校验模块,即我可以通过配置实现任意类型的CRC32校验,这里可以是大家熟知的经典的CRC32,比如CRC32-MPEG-2CRC32,也可以通过配置自创一种CRC32,自创的CRC32可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC32,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC32校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人想要破解你的CRC32校验,总是会从经典的CRC32开始尝试,哪会想到你丫居然自定义CRC32校验。
校验 - CRC32 (qq.com)
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)
啦啦啦
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-2 10:17:14 | 显示全部楼层
顶顶顶,支持支持
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-3 07:45:11 | 显示全部楼层
dingdingdingdingdingdingdingding
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-3 15:28:52 | 显示全部楼层
LRCBCC校验方法,LRC常见在hex文件中对每一条记录的校验,BCC校验常见在简单串口通信协议中。LRCBCC跟前面的CRC8/CRC16/CRC32一起通用化处理一下形成一个校验模块,对外提供统一的调用接口方便Application调用,通过简单配置即可快速实现任意类型的CRC8/CRC16/CRC32,还可自创自己的CRC8/CRC16/CRC32
校验 - LRC & BCC (qq.com)
一个通用化的CRC32校验模块,即我可以通过配置实现任意类型的CRC32校验,这里可以是大家熟知的经典的CRC32,比如CRC32-MPEG-2CRC32,也可以通过配置自创一种CRC32,自创的CRC32可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC32,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC32校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人想要破解你的CRC32校验,总是会从经典的CRC32开始尝试,哪会想到你丫居然自定义CRC32校验。
校验 - CRC32 (qq.com)
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)
啦啦啦
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-4 10:54:43 | 显示全部楼层
继续顶,继续分享
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-9 10:36:07 | 显示全部楼层
一个系统设计中最核心的东西是什么?应该是数据的组织,访问,存储方式。我理解一个系统的主要功能可以看作是对数据处理的功能,数据驱动了系统的运行。而软件只是人们借助某种语言尝试固化下来的一段自己的思维。有了“数据是易变的,而逻辑是稳定的”这样子的认知,表驱动法则是践行代码设计时保持核心+配置分开方法的最佳选择,表驱动法天生注重数据的组织,访问和存储。都说,程序=算法+数据结构,其实应该是:程序=算法(逻辑)+数据(结构)。那么到底是算法(逻辑)重要还是数据(结构)重要呢?就我自己的理解是数据(结构)要比算法(逻辑)要重要。你有牛逼的算法(逻辑),但是你的待处理的数据(结构)组织的一塌糊涂,再牛逼的算法(逻辑)它的性能也会变差,同时由于你数据结构组织的不好导致算法(逻辑)反而变得复杂;再假如你的待处理的数据组织的仅仅有条,那么你的逻辑简答了,算法明了了,性能也会上去。
代码设计时考虑数据(结构)优先(qq.com)
所有的设计模式均是为扩展(新增)/维护(变更)而生,但是会牺牲部分实时性,但是随着现在MCU的发展(主频越来越快,内存越来越多),算力越来越强,这点牺牲也就不是那么重要了。为了方便扩展(新增)/维护(变更),代码设计均是core + configuration的结构,即核心代码逻辑/算法 + 数据配置表的方式。
代码设计时的扩展性和维护性的考虑 (qq.com)
LRC
BCC校验方法,LRC常见在hex文件中对每一条记录的校验,BCC校验常见在简单串口通信协议中。LRCBCC跟前面的CRC8/CRC16/CRC32一起通用化处理一下形成一个校验模块,对外提供统一的调用接口方便Application调用,通过简单配置即可快速实现任意类型的CRC8/CRC16/CRC32,还可自创自己的CRC8/CRC16/CRC32
校验 - LRC & BCC (qq.com)
一个通用化的CRC32校验模块,即我可以通过配置实现任意类型的CRC32校验,这里可以是大家熟知的经典的CRC32,比如CRC32-MPEG-2CRC32,也可以通过配置自创一种CRC32,自创的CRC32可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC32,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC32校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人想要破解你的CRC32校验,总是会从经典的CRC32开始尝试,哪会想到你丫居然自定义CRC32校验。
校验 - CRC32 (qq.com)
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc
搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C
语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C
语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-10 10:04:06 | 显示全部楼层
一个系统设计中最核心的东西是什么?应该是数据的组织,访问,存储方式。我理解一个系统的主要功能可以看作是对数据处理的功能,数据驱动了系统的运行。而软件只是人们借助某种语言尝试固化下来的一段自己的思维。有了“数据是易变的,而逻辑是稳定的”这样子的认知,表驱动法则是践行代码设计时保持核心+配置分开方法的最佳选择,表驱动法天生注重数据的组织,访问和存储。都说,程序=算法+数据结构,其实应该是:程序=算法(逻辑)+数据(结构)。那么到底是算法(逻辑)重要还是数据(结构)重要呢?就我自己的理解是数据(结构)要比算法(逻辑)要重要。你有牛逼的算法(逻辑),但是你的待处理的数据(结构)组织的一塌糊涂,再牛逼的算法(逻辑)它的性能也会变差,同时由于你数据结构组织的不好导致算法(逻辑)反而变得复杂;再假如你的待处理的数据组织的仅仅有条,那么你的逻辑简答了,算法明了了,性能也会上去。
代码设计时考虑数据(结构)优先(qq.com)
所有的设计模式均是为扩展(新增)/维护(变更)而生,但是会牺牲部分实时性,但是随着现在MCU的发展(主频越来越快,内存越来越多),算力越来越强,这点牺牲也就不是那么重要了。为了方便扩展(新增)/维护(变更),代码设计均是core + configuration的结构,即核心代码逻辑/算法 + 数据配置表的方式。
代码设计时的扩展性和维护性的考虑 (qq.com)
LRC
BCC校验方法,LRC常见在hex文件中对每一条记录的校验,BCC校验常见在简单串口通信协议中。LRCBCC跟前面的CRC8/CRC16/CRC32一起通用化处理一下形成一个校验模块,对外提供统一的调用接口方便Application调用,通过简单配置即可快速实现任意类型的CRC8/CRC16/CRC32,还可自创自己的CRC8/CRC16/CRC32
校验 - LRC & BCC (qq.com)
一个通用化的CRC32校验模块,即我可以通过配置实现任意类型的CRC32校验,这里可以是大家熟知的经典的CRC32,比如CRC32-MPEG-2CRC32,也可以通过配置自创一种CRC32,自创的CRC32可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC32,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC32校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人想要破解你的CRC32校验,总是会从经典的CRC32开始尝试,哪会想到你丫居然自定义CRC32校验。
校验 - CRC32 (qq.com)
一个通用化的CRC16校验模块,即我可以通过配置实现任意类型的CRC16校验,这里可以是大家熟知的经典的CRC16,比如CRC16-IBM,CRC16-USB,CRC16-MODBUS,CRC16-MAXIM,CRC16-CCITT,CRC16-CCITT-FALSE,CRC16-XMODEM,CRC16-X25,CRC16-DNP也可以通过配置自创一种CRC16,自创的CRC16可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC16,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC16校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC16校验,总是会从经典的CRC16开始尝试,不会想到你居然自定义了CRC16校验。
校验 - CRC16 (qq.com)
一个通用化的CRC8校验模块,即我可以通过配置实现任意类型的CRC8校验,这里可以是大家熟知的经典的CRC8,比如CRC8-ITUCRC8-ROHCCRC8CRC8-MAXIM。也可以通过配置自创一种CRC8,自创的CRC8可能由于Poly多项式选择不是很合适,也许校验效果不如经典的CRC8,但是由于是自创的缘故(Poly多项式,输入是否按bit位反转,输出异或值的选择都是你自己定的),我理解它有个优势就是别人即使知道你用了CRC8校验,但是他猜不到到底用了哪一种,所以在防止别人破解你的校验方法上有一定的出其不意的效果,毕竟一般人破解你的CRC8校验,总是会从经典的CRC8开始尝试,不会想到你自定义了CRC8校验。
校验 - CRC8 (qq.com)
前面用mkit.bat脚本调用gcc编译链接生成exe,这种方式的缺点是只能将所有的.h/.c文件都放在工程目录下,当多个模块以文件夹形式组织代码结构时是无法使用的。所以我重点推荐使用Makefile脚本来编译链接生成exeMakefile定义生成target.bin的目标,它的依赖是所有的.obj文件,它的规则是调用gcc链接所有的.obj文件生成exe文件然后将exe文件变换为bin文件。递归展开来,每一个.obj文件又是一个目标,它的依赖是对应的.c/.s文件,它的规则是调用gcc编译对应的.c/.s文件生成.obj文件。
每次有新的模块加入工程时,只需要在INCDIRS/SRCDIRS这两个变量后追加内容即可快速使用起来,相当方便,同时如果变更了编译器,那么简单修改配置信息即可再次使用。
我的本地C环境II-Makefile脚本编译链接方式(qq.com)
vscode+gcc
搭建一个本地C环境,vscode配置的task中调用bat脚本,bat脚本调用gcc编译链接生成exe。一来练习C编程,二来也是对MCU代码做动态代码检查。动态代码检查,其实质是将本来在MCU上运行的代码设法(一般时设置打桩函数)运行在PC上,根据你设置的输入运行出一个实际结果,并与你事先给定的预期结果比较,一致则证明你设计的代码逻辑/算法没问题;不一致则说明你的代码运行效果不符合你的设计/期望,赶快检查去吧。
动态代码检查中,你的代码已经运行起来了,逻辑/算法对不对,输出结果一目了然,同时通过语句覆盖/分支覆盖,基本所有的bug全都现行了。
我的本地C环境I - bat脚本编译链接方式(qq.com)
误差累积滤波器不是很好理解,将每次采样输入与先前输出的差值进行累积,当累积差值超过滤波器常量后,输出才会变化。即在采样输入增增减减反反复复变化的过程中,只有当累积的差值达到一定的程度时才会对输出产生影响,而哪些没有使得累积差值超过阈值的采样输入,它们对系统的输出不产生影响,也就是这些输入采样值带入系统的误差被滤除了。
软件滤波器 - 误差累积滤波器 (qq.com)
中值滤波器,对非周期性的偶然波动干扰滤波效果很好。
软件滤波器 - 中值滤波器 (qq.com)
步进滤波器,简单好理解。当输入与先前输出的差值大于步进时,输出叠加步进;当输入与先前输出的差值小于步进时,输出叠加差值。适用于当输入变化时,输出按步进台阶式稳步靠近输入的信号。
软件滤波器 - 步进滤波器 (qq.com)
低通滤波器,信号与系统中介绍过任何一个信号经傅里叶变换后均可以由及其多个正弦波叠加而来,低通滤波,从频域角度看即低频正弦波通过,高频被抑制。从时域角度看就是模拟量经过滤波器后平滑了很多,突变部分被抑制。
低通滤波器,y(n)=(1-a)*y(n-1)+a*x(n),一旦a值确定了,那么它的滤波效果在增大和减小两个方向上是一样的。假如我要分开控制增大和减小两个方向上的滤波效果,那么应该用不同的a值。
软件滤波器 - 低通滤波器 (qq.com)
前面分享的SvMallocM模块,在测试时用到了我自己出的Clib_MemsetWithDataType()函数,是因为C库中的memset只能1byte操作给数组或者内存赋值,不能2byte或者4byte操作给数组或者内存赋值,在需要对uint16_t/int16_t/uint32_t/int32_t型数组赋非0值时是有问题的,所以我自己出了一个Clib_MemsetWithDataType()来代替受限的memset()
Clib_MemsetWithDataType代替受限的memset (qq.com)
前面分享中多次出现了SvMallocM这个模块,一次在交换元素时,一次在代码风格II。它是正点原子自己实现的动态内存分配,我搞明白后按照自己的风格简单改了改,形成了SvMallocM这个动态内存分配管理模块,根据前面分享的模块命名规则我打算放在我应用程序的sevice层,实现对一个定义在静态存储区的大数组(模拟内存池)进行动态内存分配和回收。
我的动态内存分配模块SvMallocM (qq.com)
前面我在的C代码风格I中曾经提到过C代码中出现的bug,有很大一部分是搞错了操作的变量类型导致的,比如操作时引起得溢出,截断,隐式得强制类型转换。
C代码bug,大多都是搞错了操作的变量类型 (qq.com)
日常开发中最让人头疼的就是命名,起名字,前面分享了我在使用的变量/函数命名规则(我的C代码风格I),今天续上前面遗留的尾巴,继续分享模块/文件命名规则。
我的C代码风格II(qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了3个从数组中查找元素的方法
查找 - 通用化的查找的解释 (qq.com)
将前面的3个查找方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
同时举个简单的查找的例子。一个序列[0,2,4,6,8,10,12],查找目标8
顺序查找时按序列下标依次查找,顺序查找直到第5次找到目标。
二分查找第1次与第1个元素0比较不相等,区间向后二分后第2次与第4个元素6比较还不相等,区间向后二分后第3次与第6个元素10比较还不相等,区间先前二分后第4次与第5个元素8比较相等了,二分查找直到第4次找到目标。
线性插值查找第1次与第1个元素0比较不相等,线性插值计算下一个比较的元素下标,公式为(key_val-left_val)/(right_val-left_val)*(right_index-left_index)(8-0)/(12-0)*(6-0)=4,第2次与第5个元素比较相等了,线性插值查找直到第2次找到目标。
线性插值查找花了2次就找到了目标,比二分查找快,二分查找又比顺序查找快。但是线性插值查找和二分查找需要序列提前按照升序排好序,同时如果升序排好序得序列中元素得线性度比较好时线性插值查找效率比二分查找要更好。
查找 - 小结 (qq.com)
查找3,从任意类型数组(普通类型数组或者结构体类型数组)中线性插值查找
查找 - 线性插值查找 (qq.com)
查找2,从任意类型数组(普通类型数组或者结构体类型数组)中二分查找
查找 - 二分查找 (qq.com)
查找1,从任意类型数组(普通类型数组或者结构体类型数组)中顺序查找
查找 - 顺序查找 (qq.com)
我正在应用并将长期坚持下去的变量/函数命名规则
我的C代码风格I(qq.com)
无聊,随便聊聊几个对我写代码产生较大影响的引路人
周末,轻松聊聊几个对我有很大影响的人 (qq.com)
MCU
的主频越来越快,memory越来越大,代码设计时的通用性/扩展性越来越重要,所以才通用化了4个对数组进行排序的方法
排序 - 通用化的初衷 (qq.com)
将前面的4个排序方法总结成一个比较统一的函数接口,方便自己需要时能快速简单的调用起来。
排序 - 小结 (qq.com)
排序4,对任意类型数组(普通类型数组或者结构体数组)进行堆排序
排序 - 堆排序 (qq.com)
排序3,对任意类型数组(普通类型数组或者结构体数组)进行插入排序
排序 - 插入排序 (qq.com)
排序2,对任意类型数组(普通类型数组或者结构体数组)进行选择排序
排序 - 选择排序 (qq.com)
排序1,对任意类型数组(普通类型数组或者结构体数组)进行冒泡排序
排序 - 冒泡排序 (qq.com)
C
语言中把元素看作一段内存来处理,通过交换两个元素对应的内存中的内容来实现交换两个元素的值
C语言中交换两个元素的值 (qq.com)
C
语言中通过定义临时变量来交换任意类型(普通类型/结构体类型)的两个变量的值
C语言中交换两个变量的值 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-13 10:10:23 | 显示全部楼层
线性插值,在数值分析中也是比较重要的一节。任何模拟量信号在X-Y坐标系中均是由无数个点(x,y)连成的一条曲线。在嵌入式MCU中使用某个模拟量信号,一是用数学函数,但是实际信号的函数表达式非常复杂,MCU计算这条曲线上的点比较复杂且耗时;二是查表法,一般都是提前计算好这条曲线上的点并存储在数组中作为一个表,使用时靠查表得到结果;三就是今天提到的线性插值法,一条曲线在X-Y坐标中经无数次分段后,就可以看作是由无数段非常短的直线拼接而来,只需要标定这几条直线的连接端点形成一个表,而这些短直线之间的点则可以用线性插值法来快速计算(y=(Y1-Y0)/(X1-X0)*(x-X0)+Y0)。这样子,即解决了查表法中表格太大的问题,且比较近似的还原了这条实际曲线;同时还解决了直接用数学函数计算过于复杂耗时的问题。
插值计算 - 线性插值 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-14 09:39:27 | 显示全部楼层
线性插值,在数值分析中也是比较重要的一节。任何模拟量信号在X-Y坐标系中均是由无数个点(x,y)连成的一条曲线。在嵌入式MCU中使用某个模拟量信号,一是用数学函数,但是实际信号的函数表达式非常复杂,MCU计算这条曲线上的点比较复杂且耗时;二是查表法,一般都是提前计算好这条曲线上的点并存储在数组中作为一个表,使用时靠查表得到结果;三就是今天提到的线性插值法,一条曲线在X-Y坐标中经无数次分段后,就可以看作是由无数段非常短的直线拼接而来,只需要标定这几条直线的连接端点形成一个表,而这些短直线之间的点则可以用线性插值法来快速计算(y=(Y1-Y0)/(X1-X0)*(x-X0)+Y0)。这样子,即解决了查表法中表格太大的问题,且比较近似的还原了这条实际曲线;同时还解决了直接用数学函数计算过于复杂耗时的问题。
插值计算 - 线性插值 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-16 09:52:57 | 显示全部楼层
都用过C库中的strstr()函数吧?它的功能是:在母字符串中查找第一次出现子字符串的“位置”。例如从“ABCDEFG”中查找“DE”第一次出现的位置。是不是跟我前面分享过的查找系列有点像,但是它又不是。前面分享的查找是从数组中查找某一个元素,这里却是从大数组中查找某个小数组。注意strstr()的局限是只能用在字符串中(字符串就是字符数组),却不能用在其他类型的数组,比如从int16_tuint32_t、结构体类型的大数组中查找同类型的小数组是否存在。来看看我扩展的可用于任意类型的Clib_Strstr()函数吧,从任意类型的大数组中查找同类型的小数组是否存在,这里的数组类型可以是uint8_t/int8_t/ uint16_t/ int16_t/ uint32_t/ int32_t/ 自定义的结构体类型等。
Clib_Strstr代替受限的strstr (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-20 09:24:13 | 显示全部楼层
今天分享我在CANoe中做测试UDS诊断的Panel和写测试Capl脚本时自己写的两个函数(Clib_Hex2Str()Clib_Str2Hex())
为什么出这两个函数?因为我有这样的两个应用场景:
1)
CANoe测试PanelInput box中输入hex形式的UDS诊断请求,在Capl脚本转换为byte型数组并发送给ECU,那么需要Clib_Str2Hex()函数来实现hex形式字符串到byte型数组的变换;比如在panel中输入“19022F”,Capl脚本需要转换为byte型数组{0x19,0x02,0x2F},然后按照TP层分包协议转为一个单帧CAN报文[0x03,0x19,0x02,0x2F,0xAA, 0xAA,0xAA, 0xAA]并发送;
2) UDS
诊断服务响应数据在ECUTP层被分包发送后,CANoe 中的Capl脚本按照TP层协议完成组包接收,这时的诊断响应数据是在一个byte型数组中的,这时我想把诊断响应数据以一个hex形式字符串显示到CANoe测试panel的中,那么需要Clib_Hex2Str()函数来实现byte型数组到hex形式字符串的转换;比如我接受到的诊断响应CAN报文为
[0x10,0x0F,0x59,0x02,0xFF,0x9A,0x01,0x11] //
首帧
[0x21,0x27,0xD1,0x4B,0x51,0x2F,0xD1,0x4C] //
续帧
[0x22,0x51,0x2F,0x00,0x00,0x00,0x00,0x00] //
续帧
Capl
脚本按照TP层协议收包完成后的响应数据为{0x59,0x02,0xFF,0x9A,0x01,0x11,0x27,0xD1,0x4B,0x51,0x2F,0xD1,0x4C, 0x51,0x2F },使用Clib_Hex2Str()函数来转换为hex形式的字符串,那么paneloutput box中应该借助环境变量显示字符串“5902FF9A011127D14B512FD14C512F”。
UDS测试Capl脚本中常用的两个函数 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-23 09:01:58 | 显示全部楼层
今天分享我在CANoe中测试UDS 诊断的Panel,在PanelCapl脚本中应用了前面分享的那几个函数:Clib_Strstr()Clib_Hex2Str()Clib_Str2Hex()。这几个函数本来就是在写Capl是有需求才设计的,后来在C环境中测试验证成功,现在在Capl脚本中测试验证。用CaplPanel来测试UDS诊断,主要是因为我们用Arxml开发的诊断,不是用CDD文件来开发,所以在CANoe中测试UDS诊断就有点麻烦(如果有CDD文件则导入CANoe,测试UDS诊断会方便很多,但是我们只有Arxml没有CDD,对我软件开发过程中想要简单测试测试UDS诊断开发是否正确时就很尴尬,我不好处理多帧及流控帧的情况,只能写Capl)
CANoe测试Panel - 应用前面的几个函数 (qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-26 09:21:17 | 显示全部楼层
顶顶顶,自己支持一下
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-8-27 09:08:27 | 显示全部楼层
常见的,在一个成熟的系统中输出log信息时,一般log信息会包括:
1)
当前的系统时间,比如2024-07-2810:41:21:207
2)
输出log信息的模块ID,比如OS/NVM/ECUM/ASYS/SLOG/DURT
3)
当前这条loglevel,比如FATAL/ERROR/WARNING/INFO/DEBUG
4)
可根据loglevel,将log以不同的颜色区分
5)
当前这条log输出的信息字符串,比如”kernel panic”
前面一篇分享的CANoe测试UDS诊断Panel内容的最后,提到了一个Python实现的工具,帮助我处理诊断响应数据中的DTC信息到喇叭的对应信息。今天就先分享这个工具中我自己实现的一个可以输出带颜色的log信息模块,这个模块帮助我在输出信息时按照成熟的log系统一样,输出带颜色,带时间,带调用者信息,带log信息等级的log,帮助我在开发Python代码时调试、查看、分析更方便快捷。
Python分享1 - 输出带颜色的log信息(qq.com)

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-2 12:41:27 | 显示全部楼层
前一篇分享了Python实现的输出带颜色log信息的模块,这个模块可输出:
1)
当前的系统时间,比如2024-07-2810:41:21:207
2)
输出log信息的模块ID,比如OS/NVM/ECUM/ASYS/SLOG/DURT
3)
当前这条loglevel,比如FATAL/ERROR/WARNING/INFO/DEBUG
4)
可根据loglevel,将log以不同的颜色区分
5)
当前这条log输出的信息字符串,比如”kernel panic”
今天分享前面提到的应用到CANoe测试中的处理诊断响应数据到喇叭故障信息的工具实现:DiagResp2SpkDtcInfo.py,这个工具处理诊断响应数据,将其转换为24个喇叭的DTC信息或者故障信息,方便我测试DTC,验证开发的ECU代码。
Python分享2 - 处理UDS诊断响应到喇叭故障信息(qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-3 12:14:10 | 显示全部楼层
前面两篇分享了一个用Python设计实现的处理UDS诊断响应数据到喇叭的DTC或者故障状态的小工具,这个工具有两种使用场景,一种是帮助我解析“19022FUDS诊断请求得到的响应数据到喇叭的DTC信息;另一种是帮我解析“3103600CUDS诊断请求得到的响应数据到喇叭当前的故障状态(FaultStatus)信息。
今天这一篇分享我使用C语言过程中积累下来的几个用宏定义实现的位操作方法,我可以用它们快速的实现对任意普通类型(非结构体)变量的位操作。
C语言中常见的IO操作宏定义 (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-7 19:23:23 | 显示全部楼层
前面一篇分享了我在编码实践过程中积累下来的几个用宏定义实现的位操作方法,我可以使用它们快速的实现对任意普通类型(构造类型除外)变量的位操作。今天这一篇分享我曾经学习C编程的表驱动法时写着玩的可控制舍入level的分数除法(Fraction divider)。一般我们对小数都是四舍五入处理的,其实在有些情形下我们也是可以根据需要实现其他类型的舍入控制的,比如二舍三入,八舍九入等。
分数除法 (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-10 13:31:35 | 显示全部楼层
MCU应用中最常见按键消抖,即读取到连续多次按下/释放才认为是按下/释放;MCU监控供电电压的状态,如连续多次采样到过压才认为过压;CAN/LIN通信中连续几帧收到某个信号的值为x才认为这个信号为x。这其中都有消抖的概念:即连续多次发生或出现才确认。思维发散开来,凡是能将某个“东西”转换为离散的值,那么就可以用多次出现/发生才确认的思路来进行消抖。这里的“东西”可以是机械按键输出、矩阵按键的输出、电压、通信总线信号、传感器的数字/模拟输出,只要能将它们转换为可消抖的离散值,那么就可以对这个“东西”进行消抖处理。今天就分享一个可以应对多种情形的通用消抖模块。
通用的消抖器 (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-20 09:24:42 | 显示全部楼层
FSM有限状态机:其要素有状态、事件、执行的动作、转移的下一个状态。当建立状态转移表后,其实现方式有两种,一种是在当前的状态中判断已发生的事件,另一种是在已发生的事件中判断当前的状态。两种实现方式实现的功能完全相同,但是在状态中判断事件时由于代码的写法(if-else中判断那个事件发生写在前面)的原因导致事件有了隐含的优先级,其实事件本身是随机发生的,并没有优先级之说;而在事件中判断判断状态,由于事件发生时的状态是确定且唯一的,根据其执行动作并进行状态的迁移,思路清晰,简洁高效。
FSM有限状态机 (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-9-27 10:51:22 | 显示全部楼层
一个我曾经使用过的将“模拟量”离散化的简单方法 –Range Index。这里的“模拟量”不是指真实环境中的真实模拟量(比如真实的电压、真实的温度等),这些真实的模拟量需要通过MCUADC外设或者外挂的ADC芯片进行采样才能得到数字量;我这里的“模拟量”是代码中代表某个东西的变量值,它的范围连续,看起来就像个模拟量一样,只是我们想把它离散化到[Dmin,Dmax)。已知“模拟量”的最大值Amax,最小值Amin,离散化后的“数字量”最大值Dmax,最小值Dmin,假若当前的“模拟量”值为Val,那么离散化后的“数字量”的值为(Dmax-Dmin)*(Val-Amin)/(Amax-Amin)+Dmin。离散化后配合其他逻辑可快速实现对“模拟量”范围调整的需求。
杂项1 - RangeIndex (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-10-15 09:35:54 | 显示全部楼层
一个用PyQt5实现的简单的数据变换的工具箱,因为在用CANoe或者PCAN测试UDS诊断时,CAN/LIN总线上收发的数据总是Hex/Dec格式显示,时不时的需要进行格式转换(Hex<==>DecHex<==>Bin Ascii <==>Hex   Float<==>Hex),否则观察起来不是很友好,所以有了做一个数据变换工具箱的想法。
PyQt5_1- ToolsBox 之 数据变换工具箱(qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-10-22 15:38:37 | 显示全部楼层

一个用PyQt5设计模仿的Tester,UDS诊断时的Client。由于缺少CANoe,license,当只有PCAN时,无法对ECU进行UDS诊断,PCAN需要...

一个用PyQt5设计模仿的Tester,UDS诊断时的Client。由于缺少CANoe,license,当只有PCAN时,无法对ECU进行UDS诊断,PCAN需要写脚本实现收发多帧报文和流控报文,所以需要设计一个GUI+Python驱动PCAN来实现基本的UDS诊断的多帧报文和流控报文的收发。另外UDS也可以基于最普通的Uart实现,在没有CAN接口的MCU上实现UDS诊断,所以需要设计一个GUI+Python驱动Serial来实现基本的UDS诊断的多帧报文和流控报文的收发来作为Client去测试ECU。所以Uart_Tester是一个我自己设计的GUI,利用Python驱动PCAN或者Serial实现的可充当简易的UDS诊断的Client上位机。
PyQt5_2- 我的Tester之Uart_Tester (qq.com)
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-11-1 14:26:39 | 显示全部楼层
电压状态的监控,电压所处的区间有欠压\滞回\正常\滞回\过压这几个区间。一般情形:从过/欠压区间进入正常区间时均会经过滞回区间,所以在滞回区间锁定消抖计数器的值到消抖的最大次数,进入正常区间后再增加消抖计数器的值。同样的,从正常区间进入过/欠压区间时也均会经过滞回区间,所以在滞回区间锁定消抖计数器的值到消抖的最大次数,进入过/欠压区间后再减小消抖计数器的值。特殊情形:电压变化非常的迅速,从过/欠压区间直接进入正常区间并未经过滞回区间,这时先将消抖计数器复位到消抖的最大次数然后开始增加消抖计数器的值;同样的,从正常区间直接进入过欠/压区间并未经过滞回区间,这时先将消抖计数器复位到消抖的最大次数然后开始减小消抖计数器的值;电压从过/欠压区间直接进入欠/过压区间并未经过滞回和正常区间,这时消抖计数器保持在0,电压状态直接切换。等消抖计数器的值减小到0或增大到消抖最大值的2倍,则认为消抖完成,得到电压监控的稳定状态。
电压状态监控

He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

26

主题

1533

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6369
金钱
6369
注册时间
2015-8-25
在线时间
1004 小时
 楼主| 发表于 2024-11-13 18:01:52 | 显示全部楼层
电压区间段监控,用来处理系统中存在多个电压区间段,并且每个区间段之间都有滞回区间的情形。比如在某个应用中,存在多个区间段(比如有区间1/区间2/区间3/区间4/区间5/区间6且每个区间段之间都有滞回区间)需要监控并实时的得出当前稳定的电压区间段的值。扩展开来,对某些应用,也可以将多个电压区间段合并使用,这时候只需要增加一个中间层的适配模块来将电压区间段监控的结果变换为应用需要的区间段或状态,比如将区间段126变换为停止工作状态,区间段35变换为工作但数据不可靠状态,区间段4变换为工作且数据可靠状态;或者将区间段12356变换为无数据状态,区间段4变换为有数据状态。反正只要电压区间段监控模块提供消抖后的稳定的区间段值,那么应用层就可根据这个区间段值做任何应用逻辑而不用操心底层实际的电压是咋变化的。
电压区间段监控
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 11:24

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表