STM32:硬件-IIC详解 , 固件库编程 , 手把手教你实现IIC

  • 一 、I2C物理层
  • 二、协议层
    • 1、I2C基本读写过程
      • (1)主机写数据到从机
      • (2)主机由从机中读数据
      • (3)I2C 通讯复合格式
    • 2、通讯的起始和停止位
    • 3、数据有效性
    • 4、地址及数据方向
    • 5、响应
  • 三、 通讯过程
    • 1、主发送器
      • 流程:
    • 2、主接收器
      • 流程:
  • 四、程序猿的语言
    • I2C_InitTypeDef 结构体:
    • (1)void I2C_GPIO_Config(void)
    • (2)void I2C_GPIO_Config(void)
    • (3)void I2C_Config(void)
    • (4)void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr)
    • (5)void I2C_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr);
  • 五、测试
    • 测试函数
    • 测试结果

一 、I2C物理层

  I2C 通讯设备之间的常用连接方式见图:

  有以下特点:(参考数据手册:上拉电阻一般4.7k~10k ,一般4.7k)
  (1)由两条总线控制:一条双向串行数据线(SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
  (2)I2C总线上可挂在多个 I2C通讯的设备,如图所示。
  (3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
  (4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,输出高阻态,而当所有设备都空闲,都输出高阻态,上拉电阻将总线拉成高电平。
  (5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
  (6)具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。

二、协议层

1、I2C基本读写过程

  阴影部分代表数据由主机传输至从机,无阴影部分相反

(1)主机写数据到从机

(2)主机由从机中读数据

(3)I2C 通讯复合格式

  简单理解,当配置I2C完成后,发出开始信号(S)。主机开始广播某特定地址的从机(SLAVE ADDERSS),并给出要做出的操作(R/W),当从机收到主机的广播地址后,会通过内部地址比较器与自身地址比较,如果主机不是呼叫自己,那么自身保持“高阻”不接受应答。若从机发现主机呼叫的是自己,那么将SDA和SCL总线拉低,表示占用总线,接受响应并产生应答信号。执行读写操作。
  若执行的是写操作,参考图(1)当从机发出应答信号给主机,主机得到了应答后,开始给该从机发送数据,从机获取数据,应答主机(告诉主机 :自己收到了数据)。如此循环主机一直发送数据给从机,直到从机发出“非应答信号”(数据够了,我不要了,停止吧)主机发出停止信号,表示停止发送。
  若执行的是读操作,参考图(2)当从机发出应答信号给主机,并发送主机需要的数据DATA,当主机接受到来自从机的数据DATA后,会应答从机(表示:好的,你的数据我已接受,你继续)。如此循环,一直接受数据。知道主机发出“非应答信号”(表示:感谢,我的数据已经够了,你停把)。然后给从机发出停止信号。
  对于复合操作,参考(1)(2).

2、通讯的起始和停止位

  起始(S)和停止§信号是两种特殊的状态。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

3、数据有效性

  I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。

4、地址及数据方向

  I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。(下图以七位地址为例)

5、响应

  I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。

  传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

三、 通讯过程

  对于实现I2C通讯,该部分十分重要。编程过程基本都是按照下图进行操作

1、主发送器

流程:

  (1)控制产生起始信号(S),它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
  (2)发送设备地址,若有从机应答,则产生事件“EV6”及“EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 为 1 表示地址已经发送,TXE 为 1 表示数据寄存器为空;
  (3)以上步骤正常执行并对 ADDR 位清零后,向 I2C 的“数据寄存器 DR”写入要发送的数据,这时 TXE 位会被重置 0,表示数据寄存器非空,I2C 外设通过SDA 信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,重复这个过程,可以发送多个字节数据;
  (4)当我们发送数据完成后,控制 I2C 设备产生一个停止信号(P),这个时候会产生EV8_2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。
  假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来判断是哪一个事件。

2、主接收器

流程:

  (1) 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
  (2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”这时SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。
  (3) 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件,SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控制 I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收数据,若非应答,则停止传输;
  (4) 发送非应答信号后,产生停止信号(P),结束传输。

  看到这里的小伙伴,恭喜你,你的耐力不错,竟然看完了这么多的理论知识,你可能也会感慨,这I2C也忒难搞了吧。一会发送这个东西,一会又检测另一个事件。要搞完一次 I2C通讯,岂不是要肝到天荒地老,哈哈哈哈。不要担心,其实很多工作,完全不需要我们做,官方已经为我们写好了大量的库,我们只需要进行“CV编程开发”即可,再加上一点小小的理解。就可以很轻松的实现I2C通讯了。

四、程序猿的语言

按着步骤走很简单的:

I2C_InitTypeDef 结构体:

typedef struct
{uint32_t I2C_ClockSpeed; uint16_t I2C_Mode;               uint16_t I2C_DutyCycle;           uint16_t I2C_OwnAddress1;                                            uint16_t I2C_Ack;                                          uint16_t I2C_AcknowledgedAddress;
}I2C_InitTypeDef;

  把每个结构体成员都配置一下就好,具体怎么配置,.h文件里都有对应的宏,Ctrl+CV即可

  OKK说完这些,程序猿的事情,当然要代码交流了!

(1)void I2C_GPIO_Config(void)

void I2C_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStruct;//初始化 IIC_GPIO 时钟RCC_APB2PeriphClockCmd(I2Cx_GPIO_SCL_Clk|I2Cx_GPIO_SDA_Clk,ENABLE);//初始化IIC_SCLGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStruct.GPIO_Pin = I2Cx_GPIO_SCL_Pin;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(I2Cx_GPIO_SCL_Port,&GPIO_InitStruct);//初始化IIC_SDA GPIO_InitStruct.GPIO_Pin = I2Cx_GPIO_SDA_Pin;GPIO_Init(I2Cx_GPIO_SDA_Port,&GPIO_InitStruct);
}

(2)void I2C_GPIO_Config(void)

void I2C_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStruct;//初始化 IIC_GPIO 时钟RCC_APB2PeriphClockCmd(I2Cx_GPIO_SCL_Clk|I2Cx_GPIO_SDA_Clk,ENABLE);//初始化IIC_SCLGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStruct.GPIO_Pin = I2Cx_GPIO_SCL_Pin;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(I2Cx_GPIO_SCL_Port,&GPIO_InitStruct);//初始化IIC_SDA GPIO_InitStruct.GPIO_Pin = I2Cx_GPIO_SDA_Pin;GPIO_Init(I2Cx_GPIO_SDA_Port,&GPIO_InitStruct);
}

(3)void I2C_Config(void)

void I2C_Config(void)
{I2C_InitTypeDef I2C_InitStruct;I2C_GPIO_Config();//配置SDA和SCL GPIO//初始化IIC外设时钟RCC_APB1PeriphClockCmd(DEBUG_I2Cx_Clk,ENABLE);//初始化IIC 结构体I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;               //使能应答I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;   //IIC 7 位寻址I2C_InitStruct.I2C_ClockSpeed = DEBUG_I2C_ClockSpeed;//通信速率 400kI2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;        //占空比 1/2I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;             //选择IIC模式I2C_InitStruct.I2C_OwnAddress1 = DEBUG_I2Cx_Addr;//输入主机地址,与从机有所区别即可正常通信I2C_Init(DEBUG_I2Cx_Port,&I2C_InitStruct);//使能IICI2C_Cmd(DEBUG_I2Cx_Port, ENABLE);
}

(4)void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr)

void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr)
{//读一个字节while(I2C_GetFlagStatus(DEBUG_I2Cx_Port, I2C_FLAG_BUSY));//发送Start信号I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);//等待EV5事件:IIC开始信号已经发出 (I2C_SR1内SB位置1)while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送7位“EEPROM地址”I2C_Send7bitAddress(DEBUG_I2Cx_Port,DEBUG_EEPROM_Addr,I2C_Direction_Transmitter);//等待EV6事件:表示地址已经发送while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//写入EEPROM内将要写入的地址数据I2C_SendData(DEBUG_I2Cx_Port,WriteAddr);//等待EV8事件:返回SET则数据寄存器DR为空  while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);//写入数据I2C_SendData(DEBUG_I2Cx_Port,*pBuffer);//等待EV8事件:返回SET则数据寄存器DR为空while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);//一个字节发送完成,发送Stop信号I2C_GenerateSTOP(DEBUG_I2Cx_Port, ENABLE);
}

(5)void I2C_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr);

/*** @brief   从EEPROM里面读取一块数据 * @param   *     @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针*     @arg WriteAddr:接收数据的EEPROM的地址* @retval  无*/
void I2C_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr)
{//发送Start信号I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);//等待EV5事件:IIC开始信号已经发出 (I2C_SR1内SB位置1)while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送7位“EEPROM地址”I2C_Send7bitAddress(DEBUG_I2Cx_Port,DEBUG_EEPROM_Addr,I2C_Direction_Transmitter);//等待EV6事件:表示地址已经发送while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//写入EEPROM内存“单元地址”I2C_SendData(DEBUG_I2Cx_Port,ReadAddr);//等待EV8事件:数据寄存器DR为空   ,地址数据已经发送while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);//重新发送Start信号I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);//等待EV5事件while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送7位“EEPROM地址”I2C_Send7bitAddress(DEBUG_I2Cx_Port,DEBUG_EEPROM_Addr,I2C_Direction_Receiver);//注意方向//等待EV6事件(接收):表示地址已经发送while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);//注意方向//产生非应答I2C_AcknowledgeConfig(DEBUG_I2Cx_Port, DISABLE);//发送Stop信号I2C_GenerateSTOP(DEBUG_I2Cx_Port, ENABLE);//等待EV7事件, BUSY, MSL and RXNE flagswhile(I2C_CheckEvent(DEBUG_I2Cx_Port, I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR); *pBuffer = I2C_ReceiveData(DEBUG_I2Cx_Port);//重新初始化 为下次做准备I2C_AcknowledgeConfig(DEBUG_I2Cx_Port, ENABLE);
}

五、测试

测试函数

#include "stm32f10x.h"
#include "bsp_i2c.h"
#include "bsp_usart.h"uint8_t I2C_Buf_Write[256];
uint8_t I2C_Buf_Read[256];
uint16_t i=0;
void delay(uint32_t count)
{while(count--);
}
int main(void)
{USART_Config();I2C_Config();for(i=0;i<256;i++){I2C_Buf_Write[i] = i;}printf("\r\n 这是一个I2C外设(AT24C02)读写测试例程 \r\n");I2C_ByteWrite(&I2C_Buf_Write[2],0x55);delay(0xffff);//因为STM32处理速度远大400k 所以,等待写入完成。I2C_ByteRead(&I2C_Buf_Read[2],0x55);printf("0x%x ", I2C_Buf_Read[2]);while(1);
}
//读写函数为测试学习使用,所有还有很多不完善的地方,改进中.....

测试结果

  在实现过程中,对应的I2Cx、时钟、GPIO选取等,全都在"bsp_i2c.h"、"bsp_usart.h"
  你们可以结合自己的硬件,配置对应的参数。串口配置,是为了调试方便。

#ifndef _BSP_I2C_H
#define _BSP_I2C_H#include "stm32f10x.h"#define I2Cx_GPIO_SCL_Port                    GPIOB
#define I2Cx_GPIO_SCL_Pin                   GPIO_Pin_6
#define I2Cx_GPIO_SCL_Clk                   RCC_APB2Periph_GPIOB#define I2Cx_GPIO_SDA_Port                  GPIOB
#define I2Cx_GPIO_SDA_Pin                   GPIO_Pin_7
#define I2Cx_GPIO_SDA_Clk                   RCC_APB2Periph_GPIOB#define DEBUG_I2Cx_Addr                     0xB0
#define DEBUG_EEPROM_Addr                   0xA0  //7位地址1010000#define DEBUG_I2C_ClockSpeed             400000#define DEBUG_I2Cx_Port                       I2C1
#define DEBUG_I2Cx_Clk                      RCC_APB1Periph_I2C1void I2C_GPIO_Config(void);
void I2C_Config(void);
void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr);
void I2C_ByteRead(uint8_t *pBuffer, uint8_t WriteAddr);
#endif /*_BSP_I2C_H*/

  参考书籍:《【野火®】零死角玩转STM32—F103霸道_V2》。有需要的小伙伴,可以留下你的邮箱,我会第一时间发过去。《AT24C02数据手册》。

  关于 模拟 “软件I2C” ,过几天我会再写一篇,大家可以先点个关注(⊙o⊙)
欢迎交流探讨。

/********************************* 更新 *************************************/

  其实I2C通讯过程,理解时序,可以帮助你了解I2C的工作过程。这个时候,你可以参考AT24C02数据手册,看下他的时序图。然后参考软件实现I2C,他是如何生成各种信号。大概过程如下(仅帮助理解,我当时梳理思路的时候写的,不能运行的那种,有一点需要补充DELAY,其实就是 WaitAck() 等待应答,当时随手写的)

//EEPROM 送入一个字节
void EE_Send_Byte(uint8_t data)
{uint8_t i = 0;for(i = 0; i < 8; i++){if(data&0x80){SDA = 1;}else{SD = 0;}DELAY;SCL = 1;DEALY;SCL = 0;if(i == 7){SDA = 1;}data <<= 1;DELAY;}
}
//EEPROM 读一个字节
uint8_t EE_Read_Byte(void)
{uint8_t i = 0;uint8_t value = 0;for(i = 0; i < 8; i++){value <<= 1;SCL = 1;DEALY;if(SDA_Read == 1){value++;}SCL = 0;DEALY;    }   return value;
}
/* 开始信号*/
void I2C_Start(void)
{SDA = 1;SCL = 1;DEALY;SDA = 0;DELAY;SCL = 0;DELAY;
}/* 停止信号*/
void I2C_Stop(void)
{SDA = 0;SCL = 1;DEALY;SDA = 1;
}/* 应答信号*/
void I2C_Ack(void)
{SDA = 0; //释放总线DEALY;SCL = 1;DEALY;SCL = 0;DEALY;SDA = 1;
}
/* 非应答信号*/
void I2C_NAck(void)
{SDA = 1; //释放总线DEALY;SCL = 1;DEALY;SCL = 0;DEALY;
}
uint8_t I2C_WaitAck(void)
{uint8_t re;SDA = 1; //释放总线DEALY;SCL = 1;DEALY;if(Read_SDA){re = 1;}else {re = 0;}SCL = 1;DEALY;return re;
}

  参考:野火的《零死角玩转STM32—F103霸道_V2》,个人感觉野火家的教程很详细,B站也有相关视频,可以参考

  如有侵权请联系我

【STM32-I2C学习总结】STM32:硬件-IIC详解 , 固件库编程 , 手把手教你实现IIC相关推荐

  1. 第018课 ADC和触摸屏硬件原理详解及裸机编程

    第001节_ADC硬件原理 模数转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件. 通常的模数转换器是把经过与标准量比较处理后的模拟量转换成以二进制数值表示的离散信号 ...

  2. 【STL学习】堆相关算法详解与C++编程实现(Heap)

    堆简介 堆并不是STL的组件,但是经常充当着底层实现结构.比如优先级队列(Priority Queue)等等. 堆是一种完全二叉树,因此我们可以用数组来存储所有节点.在这里的实现中,采用了一个技巧:将 ...

  3. 【STM32】标准库与HAL库对照学习教程八--串口通信详解

    [STM32]标准库与HAL库对照学习教程八--串口通信详解 一.前言 二.准备工作 三.通信的基本概念 1.通信方式 2.串行通信与并行通信 (1)串行通信 (2)并行通信 3.异步通信与同步通信 ...

  4. STM32最小系统硬件组成详解

    STM32最小系统硬件组成详解 0组成: 电源   复位   时钟    调试接口  启动 1.电源 : 一般3.3V  LDO供电   加多个0.01uf去耦电容   2.复位:有三种复位方式:上电 ...

  5. STM32开发实战:W25Q32JV SPI Flash详解

    STM32开发实战:W25Q32JV SPI Flash详解 在STM32单片机的应用中,使用SPI Flash能够有效地扩展程序和数据存储空间.W25Q32JV SPI Flash是一种常用的Fla ...

  6. STM32寄存器操作端口模式CRL/CRH详解

    STM32寄存器操作端口模式CRL/CRH详解 首先,在开始讲解前,大家请先看如下一段代码: #define SDA_IN_24c02(){GPIOB->CRH&=0XFFF0FFFF; ...

  7. java 检查bytebuf长度_Java学习笔记16-Netty缓冲区ByteBuf详解

    Java学习笔记16-Netty缓冲区ByteBuf详解 Netty自己的ByteBuf ByteBuf是为解决ByteBuffer的问题和满足网络应用程序开发人员的日常需求而设计的. JDK Byt ...

  8. spring学习笔记03-spring-DI-依赖注入详解(通过xml配置文件来配置依赖注入)

    spring学习笔记03-spring-DI-依赖注入详解 1.概念 2.构造函数注入 3.set方法注入 4.集合的注入 需要被注入的实体对象 package com.itheima.service ...

  9. Java NIO学习篇之缓冲区ByteBuffer详解

    定义: ByteBuffer是Buffer的实现类之一,是一个通用的缓冲区,功能要比其他缓冲区子类多.支持直接内存.是一个抽象类.子类实现是HeapByteBuffer(非直接缓冲区子类),Direc ...

最新文章

  1. 【仿汽车之家】价格区间选择控件
  2. oracle删除分区空间,Oracle 11g维护分区(三)——Dropping Partitions
  3. Improving RGB-D SLAM in dynamic environments: A motion removal approach
  4. poj3273---Monthly Expense
  5. [数据结构]合并有序数组
  6. ural 1129 (求数据)
  7. Java IdentityHashMap values()方法与示例
  8. 通过BeanPostProcessor理解Spring中Bean的生命周期及AOP原理 1
  9. 【转】Hadoop API 使用介绍
  10. LLDP发现相邻设备失败分析
  11. 尚硅谷html+css小米官网
  12. 计算机专业可以转英语吗,计算机专业英语词汇转.doc
  13. 【Red5流媒体服务器搭建】
  14. mac iwall 动态桌面引擎
  15. 基于Php+MySql数据库架构的网络验证系统
  16. user后面的计算机名更改,更改电脑用户名(可更改C:\Users\用户名)
  17. 中国Linux界的一些牛人(部分)
  18. XCTF final noxss
  19. STOP!运营小程序,不知道这4种方法,是不可能实现小程序裂变的
  20. java 椭圆焦点 求是否在圆内_椭圆焦点位置的确定

热门文章

  1. 单细胞测序技术之研究必看8篇经典综述
  2. Python 安装与使用问题须知
  3. 基于STC89C52RC的震动感应控制继电器
  4. Citrix虚拟化数据中心反病毒最佳实践
  5. Spring Boot程序中@JsonIgnoreProperties与@JsonIgnore基本使用
  6. linux怎么查看防火墙开放的端口,linux查看防火墙状态和对外开放的端口状态
  7. 东南大学与北航计算机学院,学科评估最大的赢家,有望入选双一流,有你所在的学校吗...
  8. 终端运行python
  9. Windows服务器——AD RMS服务
  10. C语言初探 之 printf压栈顺序