模拟串口LV2,解决硬件串口资源不足问题!!!!

模拟串口通信 2.0 版本!!

我在前面的文章里面有写了 虚拟串口通信,虽然说能用,但是用过的小伙伴都说 “好!”

优缺点:

先说一点,2.0版本并不适用于同硬件串口的所有场合,仅仅针对自己开发的电子垃圾的主从结构,比如自己的两块板子通信,或者硬件串口没得了又想追加串口不得已的情况。

优点:

1.可以设置任意波特率, 并且从机不可以识别任意波特率数据(只要主时钟频率高就行了)。
2.可以实现不定长度数据收发,并且用户能完成收发标志 1.0版本可不行。
3.减少资源消耗,同比1.0版本,在芯片内部外设资源上面有所减少。
4.同比1.0版本 大大提高收发数据稳定性。反正我实验没有丢过数据。

好下面说缺点:

缺点:

1. 2.0版本相对于1.0版本,硬件接线上面多出了1根时钟线(相当于 一个串口收发需要三根线,三根线,三根线,如果有多路虚拟串口,则每一路仅需要追加 2 根线,笑哭!!! )。
2. 因为是任意波特率所以无法和其他硬件串口做对接 如果非要接 那你就吧波特率规定到和硬件一样。
3. 串口通信点对点,懂得都懂。只不过用的IO模拟 ,有些引脚上面的驱动能力要强那么丢丢,一对多可试试。
好!!到这里有人就会问了:三根线,你走SPI啥!再不济你走I2C,I2C还少根线。你三根线还是模拟的数据玩个鸡毛!!
嗯?问的好!我竟无言以对,但是这是全双工通信,而且没有比较严格的主从关系,而且通信只能主机发起,不能从机发起。

好!!那又有人会问:那和SPI 有啥子区别啥,都是三线全双工通信。

好那就看下面:
在这里插入图片描述
这是一个数据收发的时序图,上面可以看到从主模块出来的TX是在时钟的下降沿发出通信起始位,然后在每个下降沿到达时改变信号状态,在时钟上升沿进行数据采集,而RX则是反过来,在上升沿发起通信并且修改电平状态,下降沿采集状态。所以在两数据通信的时候上升和下降沿都有意义。



首先是主控板 :

下面来看配置情况:

这里我定义的三个通用IO口
IO_CLK :时钟输出脚(GPIO)
IO_RX :接受输入脚(GPIO)
IO_TX :数据输出脚(GPIO)
开一个定时器 使能 更新溢出中断(定时时间自己设置一个就行)

在这里插入图片描述
在这里插入图片描述

以上就是基本的软件配置信息,这一块比较简单大家大概配置一下就行了,剩下的就是代码部分了

/* *****************************************************************
*Copyright (C), 2019-2023, TTT
*Filename:          M_File_Oled.c
*Author  :          ZY
*Created Date :     15/02/2023
*Abstract   :       处理全局参数
*History :
*V1.0.0   : Initial (by ZY,15/02/2023)
***************************************************************** */
#include "M_File_IOUart.h"

//定义结构体
struct IOUart  IOUart_h_Struct={ .IO_UART_RecvStat = COM_STOP_BIT};
/*****************************************
* 函数名: IoUartSendByte
* 功能说明: 模拟串口发送1个字节数据
* 形参:Byte 要发送的字节
* 返回值: 无
******************************************/
void IoUartSendByte(uint8_t Byte)
{
	   uint8_t tmp = 0;
		static uint8_t count = 0;
		static uint8_t Old_IO_UART_StartTXFlag  = 0;
		// 开始位
		if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT != Old_IO_UART_StartTXFlag) 
		{
			if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT == 1)
			{
				IO_UART_TXD(0); //将TXD的引脚的电平置低
				count = 0;
			}
			Old_IO_UART_StartTXFlag = IOUart_h_Struct.IO_UART_StartTXFlag1BIT;
		}
		else 
		{
			if(count < 8)	
			{				
				tmp = (Byte >> count) & 0x01;
				if(tmp==0)
				{
					IO_UART_TXD(0);
				}
				else
				{
					IO_UART_TXD(1);
				}
				count++;
			}
			else
			{
				IO_UART_TXD(1);//将TXD的引脚的电平拉高
				IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 0;   //结束 发送数据
				Old_IO_UART_StartTXFlag = 0;
				count =0;
			}
		}   
}



/*******************************************************************************
*Name :            IO_UART_SendData
*Syntax :          void  IO_UART_SendData(const char *Data,uint32_t size)
*Parameters(in) :
*Parameters(out) : 无
*Return value :    无
*Description :     IO串口发送字符串
********************************************************************************/
void  IO_UART_SendString(const char *String)
{
    for(int i=0; i<strlen(String); i++)
    {
        IoUartSendByte(String[i]);
    }
}

/*******************************************************************************
*Name :            IO_UART_SendData
*Syntax :          void  IO_UART_SendData(const char *Data,uint32_t size)
*Parameters(in) :
*Parameters(out) : 无
*Return value :    无
*Description :     IO串口发送数据
********************************************************************************/
void IO_UART_SendData(const char *Data,uint32_t size)
{
	if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 0)
	{
		IOUart_h_Struct.IO_Send_Size = size;
		memcpy(IOUart_h_Struct.IO_UART_TXBuff,Data,size);		
		IOUart_h_Struct.IO_UART_StartTXFLAGBuff = 1;
	}
}


/*******************************************************************************
*Name :            IO_UART_ReciveData
*Syntax :          void  IO_UART_ReciveData(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value :    无
*Description :     IO串口接收字符串
********************************************************************************/
void IO_UART_ReciveData(void)
{
	if(IOUart_h_Struct.IO_UART_StartRxFlag == 1)
	{
			IOUart_h_Struct.IO_UART_RecvStat++;
			if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT)
			{									
					//接收到完整的1个字节数据
					if(IOUart_h_Struct.IO_UART_RxNum < _IO_RX_LENGHT)
					{
						IOUart_h_Struct.IO_UART_RxBuff[IOUart_h_Struct.IO_UART_RxNum++] = IOUart_h_Struct.IO_UART_RecvData;	//存入缓冲区
					}
					else
					{
						IOUart_h_Struct.IO_UART_RxNum = 0;										
					}
						IOUart_h_Struct.IO_UART_StartRxFlag = 0;							
						IOUart_h_Struct.IO_UART_RecvData = 0;
						return;
					
			}
			if(IO_UART_RXD)//读取接收引脚的状态
			{
					IOUart_h_Struct.IO_UART_RecvData |= (1 << (IOUart_h_Struct.IO_UART_RecvStat - 1));
			}
			else
			{
					IOUart_h_Struct.IO_UART_RecvData &= ~(1 << (IOUart_h_Struct.IO_UART_RecvStat - 1));
			}
	}
}



/*******************************************************************************
*Name :            IO_UART_Send_Buff
*Syntax :          void IO_UART_Send_Buff(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value :    无
*Description :     IO串口发送数据处理
********************************************************************************/
void IO_UART_Send_Buff(void)
{
	 static uint8_t OLD_IO_UART_StartTXFLAGBuff = 0;
	 static uint8_t OLD_Send_Count = 0;
	 
	 if(OLD_IO_UART_StartTXFLAGBuff!= IOUart_h_Struct.IO_UART_StartTXFLAGBuff)
	 {		 
		 if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 1){
			  OLD_Send_Count = 0;			  
			 IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 1; //发送数据
		 }		 
		 OLD_IO_UART_StartTXFLAGBuff = IOUart_h_Struct.IO_UART_StartTXFLAGBuff;		
	 }
	 else
	 {
		 if(IOUart_h_Struct.IO_UART_StartTXFLAGBuff == 1)
		 {
			 if(OLD_Send_Count<IOUart_h_Struct.IO_Send_Size)
			 {
					IoUartSendByte(IOUart_h_Struct.IO_UART_TXBuff[OLD_Send_Count]);
					if(IOUart_h_Struct.IO_UART_StartTXFlag1BIT == 0)
					{						
						OLD_Send_Count++;
						if(OLD_Send_Count<IOUart_h_Struct.IO_Send_Size)
						{
							 IOUart_h_Struct.IO_UART_StartTXFlag1BIT = 1; //发送数据
						}
					}else{}		 
			 }else{
				 IOUart_h_Struct.IO_UART_StartTXFLAGBuff = 0;
				 IOUart_h_Struct.IO_Send_Size =0 ;	
			 }
		 }
	 }
}


/*******************************************************************************
*Name :            IO_UART_Time_CountDown_Deal
*Syntax :          void IO_UART_Time_CountDown_Deal(void)
*Parameters(in) :
*Parameters(out) : 无
*Return value :    无
*Description :     IO串口倒计时处理
********************************************************************************/
void IO_UART_Time_CountDown_Deal(void)
{
	if(IOUart_h_Struct.IO_UART_RxTimeOut>0)
	{
		if(IOUart_h_Struct.IO_UART_RxTimeOut == 1)
		{
			IOUart_h_Struct.IO_UART_RxTimeOut --;
			IOUart_h_Struct.IO_UART_RxOK = 1;     //一条数据接收完成
			IOUart_h_Struct.IO_UART_RxNum = 0;
			//完整接受到一条数据
			//TsUserM_h_Flag.e_u_UartAppceptFlag[0] = ON;
			return ;
		}
		IOUart_h_Struct.IO_UART_RxTimeOut--;	
	}
	else
	{
	}
}
/* *****************************************************************
*Copyright (C), 2019-2023, TTT
*Filename:          M_File_IOUart.h
*Author  :          ZY
*Created Date :     15/02/2023
*Abstract   :       处理全局参数
*History :
*V1.0.0   : Initial (by ZY,15/02/2023)
***************************************************************** */
#ifndef __M_FILE_IOUart_H__
#define __M_FILE_IOUart_H__

//#include "M_File_Flag.h"
/*自己定义导入的.h 文件 ******************************************************/


#define _IO_RX_LENGHT        200       																//接收数据长度
#define _IO_RX_TIMECOUNTDOWN  15																	//接收数据超时倒计时
/*
*@串口接收字节
*/
enum 
{
    COM_START_BIT = 0,
    COM_D0_BIT,
    COM_D1_BIT,
    COM_D2_BIT,
    COM_D3_BIT,
    COM_D4_BIT,
    COM_D5_BIT,
    COM_D6_BIT,
    COM_D7_BIT,
    COM_STOP_BIT,
};

struct IOUart{
		uint8_t 	IO_UART_RecvData; 							//接收数据
		uint8_t 	IO_UART_RecvStat;  							//接收状态		
		uint16_t    IO_UART_RxNum;								//接收数据长度
		uint8_t 	IO_UART_RxBuff[_IO_RX_LENGHT];				//模拟串口接收数据缓冲区
		uint8_t     IO_UART_TXBuff[_IO_RX_LENGHT];				//模拟串口发送数据缓冲区
	
		uint32_t 	IO_UART_RxTimeOut;            				//模拟串口接收超时计数
		uint8_t 	IO_UART_RxOK;               				//数据接收完成标志
		uint8_t 	IO_UART_StartRxFlag;						//开始接收标志,1开始接收,0不接收			
	
		uint8_t   IO_UART_StartTXFlag1BIT;						//开始发送标志,1开始发送,0不发送
		uint8_t   IO_UART_StartTXFLAGBuff;						//多字节发送
		uint16_t  IO_Send_Size;	
};

/*把下面的引脚重新指定到自己指定的引脚上面 打开宏定义 ******************************************************/
#if 0    
//读取Rx脚
#define IO_UART_RXD  HAL_GPIO_ReadPin(IO_RX_GPIO_Port,IO_RX_Pin)     //模拟串口RX端
//设置Tx脚拉高拉低
#define IO_UART_TXD(n)  if(n) HAL_GPIO_WritePin(IO_TX_GPIO_Port, IO_TX_Pin, GPIO_PIN_SET); \
													else  HAL_GPIO_WritePin(IO_TX_GPIO_Port, IO_TX_Pin, GPIO_PIN_RESET);
//设置时钟脚													
#define IO_UART_CLK(n)  if(n) HAL_GPIO_WritePin(IO_CLK_GPIO_Port, IO_CLK_Pin, GPIO_PIN_SET); \
													else  HAL_GPIO_WritePin(IO_CLK_GPIO_Port, IO_CLK_Pin, GPIO_PIN_RESET);
#endif

extern struct IOUart  IOUart_h_Struct;

extern void IO_UART_Send_Buff(void);
extern void IO_UART_SendData(const char *Data,uint32_t size);
extern void IO_UART_Time_CountDown_Deal(void);
extern void IO_UART_ReciveData(void);

#endif

定时器中断回调里面处理部分:
			HAL_GPIO_TogglePin(IO_CLK_GPIO_Port, IO_CLK_Pin);				//翻转输出时钟

			if(HAL_GPIO_ReadPin(IO_CLK_GPIO_Port, IO_CLK_Pin))      		//判断电平 当前输出为 高电平
			{
					IO_UART_Send_Buff();
			}
			else    //引脚拉低
			{
				 IO_UART_Time_CountDown_Deal(); 			
				 IO_UART_ReciveData();										 //处理虚拟串口接收数据	
				
				 if((IO_UART_RXD == GPIO_PIN_RESET)&&(IOUart_h_Struct.IO_UART_StartRxFlag == 0))  //判定是否有低电平数据进入
				 {
						if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT)			//状态为停止位
						{
							IOUart_h_Struct.IO_UART_RecvStat = COM_START_BIT;			//设置状态为起始位							
							IOUart_h_Struct.IO_UART_StartRxFlag = 1;					//开始接收					
							IOUart_h_Struct.IO_UART_RecvData = 0;  						//接收数据							
							IOUart_h_Struct.IO_UART_RxTimeOut = _IO_RX_TIMECOUNTDOWN;//接收到数据把接收超时清零																
						}		 
				 }
			}
			

上面是核心代码,如果接受不定长度代码 可在 IO_UART_Time_CountDown_Deal 函数里面 写接受完成标志。

模拟串口 printf 数据

/*******************************************************************************
Name           	 :FlagM_Uart_Printf
Syntax           :void UartM_485Printf(char *fmt,...)
Parameters(in)   :None              :-
Parameters(out)  :None              :-
Return value     :-                 :-
Description      :模拟串口数据打印
|******************************************************************************/
__align(8) char usart_txBuff[USART_TXBUFF_SIZE];                //字节对齐缓冲区
void FlagM_IOUart_Printf(char *fmt,...)
{
    uint32_t length;
    va_list ap;
    va_start(ap,fmt);
    vsprintf(usart_txBuff,fmt,ap);
    va_end(ap);
    length=strlen((const char*)usart_txBuff);   
    IO_UART_SendData(usart_txBuff,length);    
}


从机部分(时钟来来自主机):

IO_CLK :外部中断脚(EXIT)上升下降沿触发
IO_RX :接受输入脚(GPIO)
IO_TX :数据输出脚(GPIO)
在这里插入图片描述
在这里插入图片描述
基本配置比较简单

代码部分:

核心部分一样的,差异就是 主机在定时回调里面处理数据,而从机则是在外部中断中回调里处理数据

	
	if(GPIO_Pin == IO_CLK_Pin)
	{
		 if(IO_UART_CLK == GPIO_PIN_RESET)							//RX 时钟引脚为低电平  
		 {			
			  IO_UART_Time_CountDown_Deal();  						//处理虚拟串口接收数据			 
			  IO_UART_ReciveData();								//处理接受的数据
				
			  if((IO_UART_RXD == GPIO_PIN_RESET)&&(IOUart_h_Struct.IO_UART_StartRxFlag == 0))
				{
					if(IOUart_h_Struct.IO_UART_RecvStat == COM_STOP_BIT)//状态为停止位
					{
						IOUart_h_Struct.IO_UART_RecvStat = COM_START_BIT;	//设置状态为起始位
						
						IOUart_h_Struct.IO_UART_StartRxFlag = 1;					//开始接收					
						IOUart_h_Struct.IO_UART_RecvData = 0;  						//接收数据
						
						IOUart_h_Struct.IO_UART_RxTimeOut = _IO_RX_TIMECOUNTDOWN;//接收到数据把接收超时清零																
					}
				}
		 }
		 else if(IO_UART_CLK == GPIO_PIN_SET)		
		 {
			   //这里是数据发送部分 上升沿发送数据
				 IO_UART_Send_Buff(); 
		 }
	}

如有问题可以加群讨论: 764284134

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python - 面向对象编程 - 类变量、实例变量/类属性、实例属性

什么是对象和类 什么是 Python 类、类对象、实例对象 类变量、实例变量/类属性、实例属性 前言 只是叫法不一样 实例属性 实例变量 类属性 类变量 个人认为叫属性更恰当 类属性和实例属性区别 类属性&#xff0c;所有实例对象共享该属性实例属性&#xff0c;属于某一…

【MySQL】操作库 —— 库的操作 -- 详解

一、增删数据库 1、创建数据库 create database db_name; 本质就是在 /var/lib/mysql 创建一个目录。 说明&#xff1a; 大写的表示关键字。[ ] 是可选项。CHARACTER SET&#xff1a;指定数据库采用的字符集。COLLATE&#xff1a;指定数据库字符集的校验规则。 2、数据库删除…

Python||数据分析之pyecharts 绘图(词云、气泡)

1. echarts 和 Pyecharts 简介 1.1echarts 简介: • echarts 是一个使用 JavaScript 实现的开源可视化库,涵盖各行业图表,满足各种需求。 • echarts 遵循 Apache-2.0 开源协议,免费商用。 • ECharts 最初由百度团队开源,并于 2018 年初捐赠给 Apache 基金会,成为 AS…

算法day12

算法day12 二叉树理论基础114 二叉树的前序遍历145 二叉树的后序遍历94 二叉树的中序遍历迭代法 二叉树理论基础 直接看代码随想录就完事了&#xff0c;之前考研也学过&#xff0c;大概都能理解 我这里就说说代码层面的。 二叉树的存储&#xff1a; 1、链式存储&#xff1a;这…

内容检索(2024.02.12)

随着创作数量的增加&#xff0c;博客文章所涉及的内容越来越庞杂&#xff0c;为了更为方便地阅读&#xff0c;后续更新发布的文章将陆续在此做简介并附上原文链接&#xff0c;感兴趣的小伙伴们可持续关注文章发布动态&#xff1a; 信号仿真类话题如何看待频域与时域的仿真差别-…

猫头虎分享已解决Bug || Python Error: ImportError: No module named ‘module_name‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

数学建模:EWM – TOPSIS 超强讲义! 原理、应用、代码

目录 一、综合评价指标预处理 1.定量指标的一致化处理&#xff08;正向化处理&#xff09; 2.定量指标的无量纲化处理 二、熵权法&#xff08;EWM&#xff09; 三、TOPSIS法 四、熵权法-TOPSIS的使用流程 案例&#xff1a;熵权法-TOPSIS的案例分析&#xff1a;水质评价 …

贪吃蛇的实现,基于windows操作系统

前言&#xff1a; 贪吃蛇从学习到真正实现花了9天实现&#xff0c;第一二天第一次学习&#xff0c;第三四五天第二次学习&#xff0c;第六七八天一边实现一边思考&#xff0c;才完成了贪吃蛇的代码。实现了贪吃蛇以后已经接近过年&#xff0c;我想自己再根据掌握的知识制作烟花…

leetcode 461. 汉明距离

比较简单的一题&#xff0c;先对两个整数进行异或操作&#xff0c;会将两个整数二进制形式中各个数字进行异或操作&#xff0c;不同的数字则为1&#xff0c;再通过移位操作统计得到的二进制数中为1的个数&#xff0c;即为所求。 Java代码如下&#xff1a; class Solution {pub…

STM32 STD/HAL库驱动W25Q64模块读写字库数据+OLED0.96显示例程

STM32 STD/HAL库驱动W25Q64 模块读写字库数据OLED0.96显示例程 &#x1f3ac;原创作者对W25Q64保存汉字字库演示&#xff1a; W25Q64保存汉字字库 &#x1f39e;测试字体显示效果&#xff1a; &#x1f4d1;功能实现说明 利用W25Q64保存汉字字库&#xff0c;OLED显示汉字的时…

SVD奇异值分解

一、奇异值 奇异值&#xff08;Singular Values&#xff09;是线性代数中矩阵的重要性质之一&#xff0c;与奇异值分解&#xff08;SVD&#xff09;密切相关。让我们来更详细地了解一下奇异值的概念&#xff1a; 定义&#xff1a; 对于一个矩阵 ( A )&#xff0c;它的奇异值是…

【Chrono Engine学习总结】4-vehicle-4.1-vehicle的基本概念

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 1、基本介绍 Vehicle Overview Vehicle Mannel Vehicle的官方demo 1.1 Vehicle的构型 一个车辆由许多子系统构成&#xff1a;悬挂、转向、轮子/履带、刹车/油门、动…

双场板功率GaN HEMT电容模型以精确模拟开关行为

标题&#xff1a;Capacitance Modeling in Dual Field-Plate Power GaN HEMT for Accurate Switching Behavior&#xff08;TED.16年&#xff09; 摘要 本文提出了一种基于表面电位的紧凑模型&#xff0c;用于模拟具有栅极和源极场板&#xff08;FP&#xff09;结构的AlGaN/G…

JMM(Java内存模型)

Java内存模型&#xff08;Java Memory Model&#xff0c;简称JMM&#xff09;是Java语言规范中定义的一个抽象概念&#xff0c;它描述了程序中各个变量&#xff08;包括实例字段、静态字段和构成数组对象的元素&#xff09;在并发环境下的访问规则和一致性保证。JMM的主要目标是…

python+flask+django医院预约挂号病历分时段管理系统snsj0

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django/flask Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm . 第一&#xff0c;研究分析python技术&#xff0c…

点云标注工具

目录 3d手势识别 c 3d关键点&#xff0c;Bounding Box Labels Rectangle Labels KITTI 3D Ground Truth Annotator c标注工具 3d手势识别 GitHub - 99xtaewoo/Automated-Hand-3D-pose-annotation-Tool: Automated Hand 3D pose annotation Tool c 3d关键点&#xff0c;Bou…

【Django】Django文件上传

文件上传 1 定义&场景 定义&#xff1a;用户可以通过浏览器将图片等文件上传至网站。 场景&#xff1a; 用户上传头像。 上传流程性的文档[pdf&#xff0c;txt等] 2 上传规范-前端[html] 文件上传必须为POST提交方式 表单 <form> 中文件上传时必须带有 enctype…

电视上如何下载软件

电视上如何下载软件&#xff0c;告诉大家一个简单的方法&#xff0c;可以用DT浏览器下载软件&#xff0c;然后会自动安装这个软件&#xff0c;如有技术问题&#xff0c;可以免费解答

【初中生讲机器学习】8. KNN 算法原理 实践一篇讲清!

创建时间&#xff1a;2024-02-11 最后编辑时间&#xff1a;2024-02-12 作者&#xff1a;Geeker_LStar 你好呀~这里是 Geeker_LStar 的人工智能学习专栏&#xff0c;很高兴遇见你~ 我是 Geeker_LStar&#xff0c;一名初三学生&#xff0c;热爱计算机和数学&#xff0c;我们一起加…

从零开始学howtoheap:fastbins的house_of_spirit攻击3

how2heap是由shellphish团队制作的堆利用教程&#xff0c;介绍了多种堆利用技术&#xff0c;后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;优化pwn虚拟机配置支持libc等指…