IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 2021-07-29 STM32F103 I2C Software Transfer Include previous IO Clock EXIT USB firmware library -> 正文阅读

[嵌入式]2021-07-29 STM32F103 I2C Software Transfer Include previous IO Clock EXIT USB firmware library

模拟 I2C
本文展示了STM32 I2C Software Transfer 模拟 I2C
内容涉及 :
I2C 字节数据模拟输出独写 缓存读写
USART串口的识别
IO口输入输出
按键的外部中断处理
32位数据通讯,字符串通讯,单字符通讯

完整代码 :
GITthub 代码源码下载
24C02 数据包:
EEPROM 芯片文件下载

在这里插入图片描述


前言

STM32 的I2C简介 如果我们直接控制 STM32的两个 GPIO 引脚,分别用作 SCL及 SDA,按照上述信号的 时序要求,直接像控制 LED 灯那样控制引脚的输出(若是接收数据时则读取 SDA 电平),就 可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通 讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是 ST 生产的控制器还 是 ATMEL 生产的存储器, 都能按通讯标准交互。 由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态, 所以称之为“软件模拟协议”方式。 相对地,还有“硬件协议”方式,STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议, 只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

i2c通信协议,从物理层上来看这是一种非常简洁明了的通信协议。本身一共就两条总线,一条SCL(时钟总线),一条SDA(数据总线)。通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平( 原文链接:ttps://blog.csdn.net/qq_42660303/article/details/81154995)

一、 编程要点

I2C:
1) 配置通讯使用的目标引脚为开漏模式
2) 使能 I2C 外设的时钟
3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设
4) 编写基本 I2C 按字节收发的函数
5) 编写读写 EEPROM 存储内容的函数
6) 编写测试程序,对读写数据进行校验

二、使用步骤

1.理解原理图

在这里插入图片描述
在这里插入图片描述
通讯引脚
I2C 的所有硬件架构都是根据图中左侧 SCL 线和 SDA 线展开的(其中的 SMBA 线用于
SMBUS的警告信号,I2C通讯没有使用)。STM32芯片有多个 I2C外设,它们的 I2C 通讯信
号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚,见

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

I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态 时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备 将释放总线,总线再次处于空闲状态。如图所示:原文链接:https://blog.csdn.net/qq_42660303/article/details/81154995
在这里插入图片描述

在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个 时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否 定应答位。数据传输的过程如图所示:原文链接:https://blog.csdn.net/qq_42660303/article/details/81154995
在这里插入图片描述
在这里插入图片描述

(注意)Keil 配置状态

我的博客这里有项目配置 设计; 点击链接
(https://blog.csdn.net/u012651389/article/details/119189949)
在这里插入图片描述

2.建立主程序 main.c

在这里插入图片描述

代码如下 :

/**
  ******************************************************************************
  * @file    GPIO/JTAG_Remap/main.c 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * 
  ******************************************************************************
  */ 

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "PROJ_book.h" 

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */

void fn_LED_Flash_Init(void);
void fn_usart_show_Init(void);
void fn_DMA_show_Init(void);
void fn_I2C_EE_Init(void);
void fn_I2C_EE_Soft_Init(void);

uint8_t writeData[_I2C_PageSize]={4,5,6,7,8,9,10,11};
uint8_t writeData2[_I2C_PageSize]={24,25,26,27,28,29,30,31};
uint8_t ReadData[_I2C_PageSize]={0};
int main(void)
{ 
      uint8_t i=0;
      fn_RCC_Init();          //CPU 倍频
      fn_Led_Init();          //LED 输出初始化
      fn_Key_Init();          //按键 输入初始化
      fn_USART_Init();        //串口输出初始化
      fn_LED_Flash_Init();    //RGB 输出测试
      fn_usart_show_Init();   //串口输出测试
      fn_EXTI_GPIO_Config();  //外部中断入口
      fn_DMA_show_Init();     //初始化DMA数据链路
      
      
      fn_I2C_EE_Init();       //初始化硬件I2C数据链路
  
      fn_I2C_EE_Soft_Init();  //初始化软件I2C数据链路
 
       
      while(1);
}


void fn_LED_Flash_Init(void){
  uint16_t  count_Init = 2;
  printf("\n ---> LED开始运行 \n");
  while(count_Init-->0){
    fn_LED_ALL_OFF();
    __R_OUT__;
    fn_Systick_Delay(500,_Systick_ms);
    fn_LED_ALL_OFF();
    __G_OUT__;
    fn_Systick_Delay(500,_Systick_ms);
    fn_LED_ALL_OFF();
    __B_OUT__;
    fn_Systick_Delay(500,_Systick_ms);
    fn_LED_ALL_OFF();
    __R_OUT__;
  } 
}

void fn_usart_show_Init(void){ 
  fn_Usart_Send_Byte(_DEBUG_USARTx,'\r');
  printf("-->串口通信指测试完毕 \n");
  fn_Usart_SendString(_DEBUG_USARTx," : wangqi \n");
}

void fn_DMA_show_Init(void){
  printf("\n ---> DMA开始运行 \n");
  _DMA_ROM_TO_RAM(Map_BUFFER_SIZE ,aSRC_Cont_Buffer , aDST_Buffer);
  _DMA_RAM_TO_USART(Map_BUFFER_SIZE ,USART_Source_ADDR , aDST_Buffer); 
  printf("---> DMA运行完毕 \n");
}
void fn_I2C_EE_Init(void){
  printf("\n-->I2C_函数写入开始 \n");
  _I2C_EE_Init();
  I2C_Write_fun(writeData ,EEP_Firstpage ,_I2C_PageSize);
  I2C_Read_fun(ReadData ,EEP_Firstpage ,_I2C_PageSize);
  printf("--->I2C_函数写入完毕\n\r");
}

void fn_I2C_EE_Soft_Init(void){
  printf("\n-->I2C_软件函数写入开始 \n");
  I2C_Soft_Init();
  I2C_Soft_Write_fun(writeData2 ,EEP_Firstpage ,_I2C_PageSize);
  I2C_Soft_Read_fun(ReadData ,EEP_Firstpage ,_I2C_PageSize);
  printf("\n--->I2C_软件函数写入完毕\n\r");
}
void delay(int x){
	int y = 0xFFFFF;
	while((x--)>0){
		while((y--)>0){
			__NOP();
			__NOP();
			__NOP();
			__NOP();
			__NOP();
		}
	}
}
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

3.建立I2C模拟传输的 头文件 I2C_soft_book.h

代码如下 :

#ifndef  __I2C_SOFT_BOOK_H_
#define  __I2C_SOFT_BOOK_H_

#include "stm32f10x.h"
 
//---------------- 这里封装了 I2C 通讯配置信息 -------------------
 
#define   _Soft_I2C_GPIO_APBxClock_FUN   RCC_APB2PeriphClockCmd
#define   _Soft_I2C_GPIO_CLK             RCC_APB2Periph_GPIOB
#define   _Soft_I2C_SCL_PORT             GPIOB
#define   _Soft_I2C_SCL_PIN              GPIO_Pin_6
#define   _Soft_I2C_SDA_PORT             GPIOB
#define   _Soft_I2C_SDA_PIN              GPIO_Pin_7
 

#define   _I2C_SCL_1()   _Soft_I2C_SCL_PORT->BSRR = _Soft_I2C_SCL_PIN
#define   _I2C_SCL_0()   _Soft_I2C_SCL_PORT->BRR =  _Soft_I2C_SCL_PIN
#define   _I2C_SDA_1()   _Soft_I2C_SCL_PORT->BSRR = _Soft_I2C_SDA_PIN
#define   _I2C_SDA_0()   _Soft_I2C_SCL_PORT->BRR =  _Soft_I2C_SDA_PIN
#define   _I2C_SDA_READ()  ((_Soft_I2C_SCL_PORT->IDR & _Soft_I2C_SDA_PIN)!=0)

#define I2C_WR	0		/* 写控制bit */
#define I2C_RD	1		/* 读控制bit */

//----------------器件地址--------------------
/* 
 * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
 * 32 pages of 8 bytes each
 *
 * Device Address
 * 1 0 1 0 A2 A1 A0 R/W
 * 1 0 1 0 0  0  0  0 = 0XA0
 * 1 0 1 0 0  0  0  1 = 0XA1 
 */
/* EEPROM Addresses defines */
#define Soft_EEPROM_ADDRESS 0xA0   /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA2 /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA4 /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA6 /* E2 = 0 */
 
/*读取数据的格式以及字符数量*/
#define   _I2C_Soft_PageSize      8
#define   _I2C_Soft_SIZE				256			  /* 24xx02总容量 */
/*I2C 存储地址*/
#define  EEP_Soft_Firstpage      0x90

void  I2C_Soft_Init(void);
void  EE_Soft_Trase(void);
uint8_t I2C_Soft_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
uint8_t I2C_Soft_Read_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);

#endif

4.建立I2C模拟传输的 头文件 I2C_soft_book.c

代码如下 :

#include "I2C_soft_book.h"
#include "Systick_book.h"

static I2C_GPIO_Soft_Config(void){
  GPIO_InitTypeDef   GPIO_InitStructure;
  _Soft_I2C_GPIO_APBxClock_FUN(_Soft_I2C_GPIO_CLK , ENABLE);
  
  GPIO_InitStructure.GPIO_Pin = _Soft_I2C_SCL_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  //开漏输出
  GPIO_Init(_Soft_I2C_SCL_PORT,&GPIO_InitStructure);
  
  GPIO_InitStructure.GPIO_Pin = _Soft_I2C_SDA_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  //开漏输出
  GPIO_Init(_Soft_I2C_SDA_PORT,&GPIO_InitStructure);
}

/**
  * @brief  I2C_EE_Init 程序初始化
  * @param  无
  * @retval 无
  */
static void I2C_Start(void){
  // 当SCL高电平时候SDA 出现一个下降沿编号位I2C 总线启动信号
   _I2C_SCL_1();
   _I2C_SDA_1();
   fn_Systick_Delay(50,_Systick_us);
   _I2C_SDA_0();
   fn_Systick_Delay(50,_Systick_us);
   _I2C_SCL_0();
   fn_Systick_Delay(50,_Systick_us);
}


/**
  * @brief  I2C_Stop 程序初始化
  * @param  无
  * @retval 无
  */

static void I2C_Stop(void){
  // 当SCL高电平,SDA出现上升沿表示I2C总线停止信号
  _I2C_SDA_0();
  _I2C_SCL_1();
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SDA_1(); 
}


/**
  * @brief  I2C_SendByte 程序初始化
  * @param  无
  * @retval 无
  */

static void I2C_SendByte(uint8_t _ucByte){
  uint8_t i;
  //发送字节的高位
  for( i=0; i<8;i++ ){
    if(_ucByte & 0x80){
      _I2C_SDA_1();
    }else{
      _I2C_SDA_0();
    }
    fn_Systick_Delay(50,_Systick_us);
    _I2C_SCL_1();
    fn_Systick_Delay(50,_Systick_us);
    _I2C_SCL_0();
    _ucByte <<=1;               //------注意这里不太一样
    fn_Systick_Delay(50,_Systick_us);
  }
  _I2C_SDA_1();// 释放总线
}

/**
  * @brief  I2C_ReadByte 程序初始化
  * @param  无
  * @retval 无
  */
static uint8_t I2C_ReadByte(void){
  uint8_t i;
  uint8_t value;
  
  //读到第1个bit 为数据的bit7
  value = 0;
  for(i=0 ;i<8 ;i++ ){
    value <<=1;
    _I2C_SCL_1();
    fn_Systick_Delay(50,_Systick_us);
    if(_I2C_SDA_READ()){
      value++;
    }
    _I2C_SCL_0();
    fn_Systick_Delay(50,_Systick_us);
  }
  return value;
}


/**
  * @brief  I2C_WaitAck  
  * @param  无
  * @retval 无
  */

static uint8_t I2C_WaitAck(void){
  uint8_t re;
  _I2C_SDA_1();
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SCL_1();
  fn_Systick_Delay(50,_Systick_us);
  if(_I2C_SDA_READ()){
    re = 1;
  }else{
    re = 0;
  }
  _I2C_SCL_0();
  fn_Systick_Delay(50,_Systick_us);
  return re;
}


/**
  * @brief  I2C_ACK  
  * @param  无
  * @retval 无
  */

static void I2C_ACK(void){
  _I2C_SDA_0();  //CPU 驱动SDA = 0;
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SCL_1();  //CPU 产生1个时钟
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SCL_0();
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SDA_1(); //CPU 释放SDA总线
}



/**
  * @brief  I2C_ACK  
  * @param  无
  * @retval 无
  */

static void I2C_NACK(void){
  _I2C_SDA_1();  //CPU 驱动SDA = 1;
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SCL_1();  //CPU 产生1个时钟
  fn_Systick_Delay(50,_Systick_us);
  _I2C_SCL_0();
  fn_Systick_Delay(50,_Systick_us);
}

static uint8_t I2C_CheckDevice(uint8_t _Address){
  uint8_t ucAck;
  
  I2C_GPIO_Soft_Config();
  I2C_Start();
  
  I2C_SendByte(_Address | I2C_WR);
  ucAck = I2C_WaitAck();  /* 检测设备的ACK应答 */
  
  I2C_Stop(); /* 发送停止信号 */
  
  return ucAck;
}


//----------------------I2C 独写操作--------------------
//------------------------------------------------------

/**
  * @brief  EE_Soft_Check_State  
  * @param  判断串行EERPOM是否正常
  * @retval 无
  */

static uint8_t EE_Soft_Check_State(void){
  if(I2C_CheckDevice(Soft_EEPROM_ADDRESS)==0){return 1;}
  else{I2C_Stop(); /* 发送停止信号 */ return 0;}
}

/**
  * @brief  uint8_t I2C_Soft_BufferRead(uint8_t* pBuffer, 
  *      uint8_t ReadAddr, uint16_t NumByteToRead)
  * @param  判断串行EERPOM是否正常
  * @retval 无
  */
static uint8_t I2C_Soft_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead){
  uint16_t i ;
  //连续取得若干个字节
  // 发起I2C总线启动信号
  I2C_Start();
  //发送控制字节地址和读取数据信号
  I2C_SendByte(Soft_EEPROM_ADDRESS | I2C_WR);
  //等待应答状态
  if(I2C_WaitAck()!=0){printf("EEPROM 错误 1 !\r\n"); goto  CMD_Fail;}
  //发送数据读取位置信息信号
  I2C_SendByte((uint8_t)ReadAddr);
  //等待应答状态
  if(I2C_WaitAck()!=0){printf("EEPROM 错误 2 !\r\n");goto  CMD_Fail;}
  
  //--------------
  //重新启动I2C总线 
  I2C_Start();
  //发送器件地址
  I2C_SendByte(Soft_EEPROM_ADDRESS| I2C_RD);
  //等待应答状态
  if(I2C_WaitAck()!=0){printf("EEPROM 错误3 !\r\n"); goto  CMD_Fail;}
  for(i=0 ;i<NumByteToRead ;i++ ){
    pBuffer[i] = I2C_ReadByte();
    if(i!=NumByteToRead-1){
      I2C_ACK();
    }else{
      I2C_NACK();
    }
  }
  I2C_Stop();
  return 1;
 
 CMD_Fail:
  I2C_Stop();
  return 0;
}

/**
  * @brief  uint8_t EE_Soft_WriteBytes(uint8_t* pBuffer, 
  *                   uint8_t ReadAddr, uint16_t NumByteToRead)
  * @param  判断串行EERPOM是否正常
  * @retval 无
  */

static uint8_t EE_Soft_WriteBytes(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead){
  uint16_t i , m;
  uint16_t usAddr;
  /*
    写串行EEPROM 不像读操作可以连续读取很多字节每次写操作只能在同一个page
    对于24C page size = 8 
    简单的处理方法为 按字节写操作模式,写一个字节都发送地址
    为了提高连续写的效率 笨函数采用Page write操作
  */
  usAddr = ReadAddr;
  for(i=0 ;i<NumByteToRead;i++ ){
    // 当发送第一个字节或者页面首地址时,需要重新发起启动信号和地址
    if((i==0)||(usAddr)&(_I2C_Soft_PageSize-1)==0){
      // 发送停止信号
      I2C_Stop();
      //通过检测器判断内存写入是否成功
      m = 100;
      for (m = 0; m < 100; m++){	
        //启动I2C总线 
        I2C_Start();
        //发送器件地址
        I2C_SendByte(Soft_EEPROM_ADDRESS| I2C_WR);
        //等待应答状态
        if(I2C_WaitAck()==0){break;}
      }
      if(m==100){printf("EEPROM 错误 4 !\r\n"); goto CMD_FAIL_bytes ; }
      I2C_SendByte((uint8_t)usAddr);
      if(I2C_WaitAck()!=0){printf("EEPROM 错误 5 !\r\n"); goto CMD_FAIL_bytes;}
    }
    // 开始写入数据 
    I2C_SendByte(pBuffer[i]);
    //等待应答状态
    if(I2C_WaitAck()!=0){printf("EEPROM 错误 7 !\r\n"); goto CMD_FAIL_bytes;}
    usAddr++;
  }
  // 发送停止信号
  I2C_Stop();
  return 1;
  
CMD_FAIL_bytes:
  // 发送停止信号
  I2C_Stop();
  return 0;
}



/**
  * @brief  void  EE_Soft_Trase(void)  
  * @param  判断串行EERPOM是否正常
  * @retval 无
  */
void  EE_Soft_Trase(void){
  uint16_t i ;
  uint8_t buf[_I2C_Soft_SIZE]={0};
  
  // 填充缓冲区
  for(i=0 ;i<_I2C_Soft_SIZE ;i++ ){
    buf[i] = 0xFF;
  }
  //写EEPROM 起始地址= 0 数据长度为256
  if(EE_Soft_WriteBytes(buf,0,_I2C_Soft_SIZE)==0){
    printf("擦除EEPROM出错!\r\n");
		return;
  }else{
    printf("擦除EEPROM出错!\r\n");
  }
}


/**
  * @brief  void I2C_Soft_Init(void)
  * @param   
  * @retval 无
  */
void I2C_Soft_Init(void){
  if(EE_Soft_Check_State()==0){
      /* 没有检测到EEPROM */
      printf("没有检测到串行EEPROM!\r\n");
  }
}


/**
  * @brief  void I2C_Soft_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite) 
  * @param  判断串行EERPOM是否正常
  * @retval 无
  */
uint8_t I2C_Soft_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  uint16_t i;
   
  //-------------------------------
  if(EE_Soft_Check_State()==0){
    /* 没有检测到EEPROM */
		printf("没有检测到串行EEPROM!\r\n");
    return 1;
  }  
  //------------写入I2C-------------------
  if(EE_Soft_WriteBytes(pBuffer,WriteAddr ,NumByteToWrite)==0){
    /* 没有检测到EEPROM */
		printf("写EEPROM错误!\r\n");
    return 1;
  }else{
    /* 没有检测到EEPROM */
		printf("写EEPROM成功!\r\n");
  }
  fn_Systick_Delay(150,_Systick_us);
  //--------------数据检查--------------
  printf("EEPROM写入数据检查检查\r\n");
  for(i=0 ;i<NumByteToWrite ;i++ ){
     
     printf(" %d ",pBuffer[i]);
    if((i & 15)==15){
      printf("\r\n");
    }
  }
  return 0;
  
}


uint8_t I2C_Soft_Read_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  uint16_t i;
  //-------------读取I2C------------------
  if(I2C_Soft_BufferRead(pBuffer,WriteAddr,NumByteToWrite)==0){
    /* 没有检测到EEPROM */
		printf("读EEPROM错误!\r\n");
    return 1;
  }else{
    /* 没有检测到EEPROM */
		printf("\n读EEPROM成功!\r\n");
  }
  //--------------数据检查--------------
  printf("EEPROM读取数据数据检查 \r\n");
  for(i=0 ;i<NumByteToWrite ;i++ ){
     printf(" %d ",pBuffer[i]);
    if((i & 15)==15){
      printf("\r\n");
    }
  }
  return 1;
}
/*********************END OF FILE**********************/

5.建立I2C硬件传输的 头文件 I2C_book.h

代码如下 :

#ifndef  __I2C_BOOK_H_
#define  __I2C_BOOK_H_

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
 
#include "USART_book.h"

//---------------- 这里封装了 I2C 通讯配置信息 -------------------
#define   _EEPROM_I2Cx                     I2C1
#define   _EEPROM_I2C_APBxClock_FUN        RCC_APB1PeriphClockCmd    
#define   _EEPROM_I2C_CLK                  RCC_APB1Periph_I2C1
#define   _EEPROM_I2C_GPIO_APBxClock_FUN   RCC_APB2PeriphClockCmd
#define   _EEPROM_I2C_GPIO_CLK             RCC_APB2Periph_GPIOB
#define   _EEPROM_I2C_SCL_PORT             GPIOB
#define   _EEPROM_I2C_SCL_PIN              GPIO_Pin_6
#define   _EEPROM_I2C_SDA_PORT             GPIOB
#define   _EEPROM_I2C_SDA_PIN              GPIO_Pin_7

/*STM32 I2C 速度模式  */
#define   _I2C_Speed       400000
/* I2C 器件地址 */
#define   _I2Cx_OWN_ADDRESS7              0x5f
/*读取数据的格式以及字符数量*/
#define   _I2C_PageSize      8
/*I2C 存储地址*/
#define  EEP_Firstpage      0x90
#define  EEP_SIZE           0xFF
//----------------器件地址--------------------
/* 
 * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
 * 32 pages of 8 bytes each
 *
 * Device Address
 * 1 0 1 0 A2 A1 A0 R/W
 * 1 0 1 0 0  0  0  0 = 0XA0
 * 1 0 1 0 0  0  0  1 = 0XA1 
 */
/* EEPROM Addresses defines */
#define EEPROM_ADDRESS 0xA0   /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA2 /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA4 /* E2 = 0 */
//#define EEPROM_ADDRESS 0xA6 /* E2 = 0 */

//----------------函数声明--------------------
 
//I2C 应用函数
void _I2C_EE_Init(void);
void I2C_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
void I2C_Read_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);

#endif

6.建立I2C硬件传输的 头文件 I2C_book.c

代码如下 :

#include "I2C_book.h"
#include "Systick_book.h"

/**
  * @brief  I2C_EE_Init 程序初始化
  * @param  无
  * @retval 无
  */
static void I2C_GPIO_Config(void){
  GPIO_InitTypeDef   GPIO_InitStructure;
  //  初始化 I2C 相关时钟
  _EEPROM_I2C_APBxClock_FUN(_EEPROM_I2C_CLK,ENABLE);
  _EEPROM_I2C_GPIO_APBxClock_FUN(_EEPROM_I2C_GPIO_CLK,ENABLE);
  //  初始化I2C_SCL SDA
  
  GPIO_InitStructure.GPIO_Pin = _EEPROM_I2C_SCL_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //开漏输出
  GPIO_Init(_EEPROM_I2C_SCL_PORT,&GPIO_InitStructure);
  
  GPIO_InitStructure.GPIO_Pin = _EEPROM_I2C_SDA_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //开漏输出
  GPIO_Init(_EEPROM_I2C_SDA_PORT,&GPIO_InitStructure);
}

/**
  * @brief  I2C_EE_Init 程序初始化
  * @param  无
  * @retval 无
  */
static void I2C_Mode_Config(void){
  I2C_InitTypeDef I2C_InitStructure;
  /* i2C 配置 */
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
  // 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
  
  I2C_InitStructure.I2C_OwnAddress1 = _I2Cx_OWN_ADDRESS7;
  
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
  
  //I2C 寻址模式
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  //通讯频率
  I2C_InitStructure.I2C_ClockSpeed = _I2C_Speed;
  //I2C 初始化
  I2C_Init(_EEPROM_I2Cx,&I2C_InitStructure);
  //使能I2C
  I2C_Cmd(_EEPROM_I2Cx,ENABLE);
}
  
/**************************************/

static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode){
  fn_Usart_SendString(_DEBUG_USARTx,"I2C 等待超时!errorCode =");
  printf("%d\n",errorCode);
 
  return 0;
}
/**************************************/

/*通讯等待超时时间*/
#define  I2CT_FLAG_TIMEOUT  ((uint32_t)0x6000)
#define  I2CT_LONG_TIMEOUT  ((uint32_t)(10*I2CT_FLAG_TIMEOUT))
static uint16_t I2CTimeout;
/**************************************/
/**
* @brief  写一个字节到 I2C EEPROM 中
* @param  pBuffer:缓冲区指针
* @param  WriteAddr:写地址
* @retval 正常返回 1,异常返回 0
*/
static uint32_t  I2C_EE_ByteWrite(u8* pBuffer, uint8_t WriteAddr ){
  
  I2CTimeout = I2CT_LONG_TIMEOUT;
  while(I2C_GetFlagStatus(_EEPROM_I2Cx , ENABLE)){
    if((I2CTimeout--) == 0){return  I2C_TIMEOUT_UserCallback(4);}
  }
 
  //产生I2C起始信号
  I2C_GenerateSTART(_EEPROM_I2Cx , ENABLE);
  I2CTimeout = I2CT_LONG_TIMEOUT; //这个变量是延时异常时间
  //检测EV5 事件并清除标识位
  while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_MODE_SELECT)){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(5);}
  } 
  
  //发送EEPROM 设备地址
  I2C_Send7bitAddress(_EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);
  I2CTimeout = I2CT_LONG_TIMEOUT; //这个变量是延时异常时间
  //检测EV6 事件并清除标识位
  while(I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(6);}
  }
  
  //发送要写入的EEPROM 内部地址(即EEPROM内部存储其地址);
  I2C_SendData(_EEPROM_I2Cx,WriteAddr);
  I2CTimeout = I2CT_LONG_TIMEOUT;
  //检测EV8 事件清除标志位
  while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(2);}
  }
   
  //发送要写入的EEPROM 内部的数据;
  I2C_SendData(_EEPROM_I2Cx,*pBuffer);
  I2CTimeout = I2CT_LONG_TIMEOUT;
  //检测EV8 事件清除标志位
  while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(3);}
  }
  
  //发送要写入的EEPROM 内部的数据;
  I2C_SendData(_EEPROM_I2Cx,ENABLE);
  
  return 1;
}

 /**
  * @brief 将缓冲区中的数据写到 I2C EEPROM 中,采用单字节写入的方式,速度比页写入慢
  * @param pBuffer:缓冲区指针
  * @param WriteAddr:写地址
  * @param NumByteToWrite:写的字节数
  */

static void I2C_EE_WaitEepromStandbyState(void){
  vu16 SR1_Tmp = 0;
  do{
    //产生I2C起始信号
    I2C_GenerateSTART(_EEPROM_I2Cx,ENABLE);
    //读取I2C1 SR1 寄存器
    SR1_Tmp = I2C_ReadRegister(_EEPROM_I2Cx, I2C_Register_SR1);
    //发送EEPROM 地址+ 方向
    I2C_Send7bitAddress(_EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);
  }while(!(I2C_ReadRegister(_EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));
  /* 清除 AF 位 */
  I2C_ClearFlag(_EEPROM_I2Cx, I2C_FLAG_AF);
  //发送停止位信号
  I2C_GenerateSTOP(_EEPROM_I2Cx , ENABLE);
}

//zuozuo04-30 

/**
* @brief 在 EEPROM 的一个写循环中可以写多个字节,但一次写入的字节数
* 不能超过 EEPROM 页的大小,AT24C02 每页有 8 个字节
* @param
* @param pBuffer:缓冲区指针
* @param WriteAddr:写地址
* @param NumByteToWrite:要写的字节数要求 NumByToWrite 小于页大小
* @retval 正常返回 1,异常返回 0
*/
static uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  I2CTimeout = I2CT_LONG_TIMEOUT;
  while(I2C_GetFlagStatus(_EEPROM_I2Cx , ENABLE)){
    if((I2CTimeout--) == 0){return  I2C_TIMEOUT_UserCallback(4);}
  }
  //产生I2C起始信号
  I2C_GenerateSTART(_EEPROM_I2Cx , ENABLE);
  I2CTimeout = I2CT_LONG_TIMEOUT; //这个变量是延时异常时间
  //检测EV5 事件并清除标识位
  while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_MODE_SELECT)){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(5);}
  }
  
  //发送EEPROM 设备地址
  I2C_Send7bitAddress(_EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);
  I2CTimeout = I2CT_LONG_TIMEOUT; //这个变量是延时异常时间
  //检测EV6 事件并清除标识位
  while(I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(6);}
  }
  
  //发送要写入的EEPROM内部地址(EEPROM内部存储器地址)
  I2C_SendData(_EEPROM_I2Cx,WriteAddr);
  I2CTimeout = I2CT_LONG_TIMEOUT; //这个变量是延时异常时间
  //检测EV7 事件并清除标识位
  while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTING)){
    if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(7);}
  }

  //循环发送 NumByteToWrite个数据
  while(NumByteToWrite--){
    //发送缓冲区的数据
    I2C_SendData(_EEPROM_I2Cx,*pBuffer++);
    I2CTimeout = I2CT_FLAG_TIMEOUT; //这个变量是延时异常时间
    //检测EV8 事件并清除标识位
    while(!I2C_CheckEvent(_EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
      if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(8);}
    }       
  }
  //发送停止信号
  I2C_GenerateSTOP(_EEPROM_I2Cx,ENABLE);
  return 1;
}



/**  快速写入一页
* @brief 将缓冲区中的数据写到 I2C EEPROM 中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
#define I2C_PageSize 8  //AT24C01 02 每页有8个字节
static void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  u8 NumOfPage = 0, NumOfSingle = 0 , Addr = 0 , count = 0,temp = 0;
  //Mod 求余运算,如果writeAddr 是 I2C_PageSize 整书倍,运算结果位Addr为0
  Addr = WriteAddr % I2C_PageSize;
  //差count个数值,刚好可以对齐到页面地址
  count = I2C_PageSize - Addr; 
  //计算出要写多少整书页
  NumOfPage = NumByteToWrite / I2C_PageSize;
  //mod运算求余计算出不满一页的字节数
  NumOfSingle = NumByteToWrite % I2C_PageSize;
  // Addr = 0,则WriteAddr 刚好按页对齐aligned
  // 这样就很简单了,直接写就可以写完整页后
  // 把剩下的不满一页的写完即可
  if(Addr == 0){
    //如果 NumByteToWrite < I2C_PageSize
    if(NumOfPage==0){
      I2C_EE_PageWrite(pBuffer , WriteAddr, NumOfSingle);
      I2C_EE_WaitEepromStandbyState();
    }//如果NumByteToWrite > I2C_PageSize
    else{
      //先把整数页写了
      while(NumOfPage--){
        I2C_EE_PageWrite(pBuffer , WriteAddr, I2C_PageSize);
        I2C_EE_WaitEepromStandbyState();
        WriteAddr += I2C_PageSize ;
        pBuffer += I2C_PageSize ;
      }
      //若有多余的不满一页的数据,把它写完
      if(NumOfSingle != 0){
        I2C_EE_PageWrite(pBuffer , WriteAddr, NumOfSingle);
        I2C_EE_WaitEepromStandbyState();
      }
    }
  }
  //如果 WriteAddr 不是按 I2C_PageSize 对齐
  //那就算出对齐到页地址还需要多少数据,然后先把这几个数据写完,剩下开始的地址就已经对齐
  //到页地址了,代码重复上面的即可
  else{
    //如果NumByteToWrite < I2C_PageSize
    if(NumOfPage == 0){
      //若NumOfSingle > count,当前面写不完,要写下一页
      if(NumOfSingle > count){
        temp = NumOfSingle - count;
        I2C_EE_PageWrite(pBuffer , WriteAddr, count);
        I2C_EE_WaitEepromStandbyState();
        WriteAddr += count ;
        pBuffer += count ;
        
        I2C_EE_PageWrite(pBuffer , WriteAddr, temp);
        I2C_EE_WaitEepromStandbyState();
      }else{//若count 比 NumOfSingle大
        I2C_EE_PageWrite(pBuffer , WriteAddr, NumByteToWrite);
        I2C_EE_WaitEepromStandbyState(); 
      }
    }
    //如果 NumByteToWrite > I2C_PageSize
    else{
      //地址不对齐多出的Count 分开处理,不加入这个运算
      NumByteToWrite -= count;
      NumOfPage = NumByteToWrite / I2C_PageSize ;
      NumOfSingle = NumByteToWrite % I2C_PageSize;
      //先把 WriteAddr 所在页的剩余字节写了
      if(count!=0){
        I2C_EE_PageWrite(pBuffer , WriteAddr, count);
        I2C_EE_WaitEepromStandbyState();
        //加上 count 后,地址就对齐到页了
        WriteAddr += count ;
        pBuffer += count ;
      }
      //把整页都写了
      while(NumOfPage--){
        I2C_EE_PageWrite(pBuffer , WriteAddr, I2C_PageSize);
        I2C_EE_WaitEepromStandbyState();
        WriteAddr += I2C_PageSize ;
        pBuffer += I2C_PageSize ;
      }
      //若多余的不满足一页,就把它写完
      if(NumOfSingle !=0){
        I2C_EE_PageWrite(pBuffer , WriteAddr, NumOfSingle);
        I2C_EE_WaitEepromStandbyState(); 
      }
    }
    
  }
}



/*  EEPROM 读取
* @brief 从 EEPROM 里面读取一块数据
* @param pBuffer:存放从 EEPROM 读取的数据的缓冲区指针
* @param ReadAddr:接收数据的 EEPROM 的地址
* @param NumByteToRead:要从 EEPROM 读取的字节数
* @retval 正常返回 1,异常返回 0
*/

static uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead){
    I2CTimeout = I2CT_LONG_TIMEOUT ;
    while(I2C_GetFlagStatus(_EEPROM_I2Cx , I2C_FLAG_BUSY)){
      if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(9);}
    }
    
    //产生I2C起始信号
    I2C_GenerateSTART(_EEPROM_I2Cx , ENABLE);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测EV10 事件并清除标注
    while(! I2C_CheckEvent(_EEPROM_I2Cx , I2C_EVENT_MASTER_MODE_SELECT)){
      if((I2CTimeout--)==0){ return I2C_TIMEOUT_UserCallback(10);}
    }
    
    //发送EEPROM 设备地址
    I2C_Send7bitAddress(_EEPROM_I2Cx , EEPROM_ADDRESS , I2C_Direction_Transmitter);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测EV11 事件并清除
    while(! I2C_CheckEvent(_EEPROM_I2Cx , I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){
      if((I2CTimeout--)==0){ return I2C_TIMEOUT_UserCallback(11);}
    }
    
    //通过重新设置PE位清除EV12事件
    I2C_Cmd(_EEPROM_I2Cx ,ENABLE );
    //发送要读取的EEPROM内部地址(即EEPROM内部存储器地址)
    I2C_SendData(_EEPROM_I2Cx, ReadAddr);
    I2CTimeout = I2CT_FLAG_TIMEOUT ;
    //检测EV12 事件并清除
    while(! I2C_CheckEvent(_EEPROM_I2Cx , I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
      if((I2CTimeout--)==0){ return I2C_TIMEOUT_UserCallback(12);}
    }
    
    //产生第二次I2C起始信号
    I2C_GenerateSTART(_EEPROM_I2Cx , ENABLE);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测EV13 事件并清除
    while(! I2C_CheckEvent(_EEPROM_I2Cx ,  I2C_EVENT_MASTER_MODE_SELECT)){
      if((I2CTimeout--)==0){ return I2C_TIMEOUT_UserCallback(13);}
    }
    
    //发送EEPROM 设备地址
    I2C_Send7bitAddress(_EEPROM_I2Cx , EEPROM_ADDRESS , I2C_Direction_Receiver);
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    //检测EV14 事件并清除
    while(! I2C_CheckEvent(_EEPROM_I2Cx , I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){
      if((I2CTimeout--)==0){ return I2C_TIMEOUT_UserCallback(14);}
    }
    
    //读取NumByteToRead个数据
    while(NumByteToRead){
      //如果 NumByteToRead=1,表示已经收到最后一个数据了
      //发送应答信号结束输出
      if(NumByteToRead == 1){
        //发送非应答信号
        I2C_AcknowledgeConfig(_EEPROM_I2Cx , DISABLE);
        
      }
      I2CTimeout = I2CT_LONG_TIMEOUT;
      while(I2C_CheckEvent(_EEPROM_I2Cx , I2C_EVENT_MASTER_BYTE_RECEIVED)==0){
        if((I2CTimeout--)==0){return I2C_TIMEOUT_UserCallback(3);}
      }
      
      //通过I2C,从设备中读取一个字节的数据
      *pBuffer = I2C_ReceiveData(_EEPROM_I2Cx);
      //存储数据的指针指以下地址
      pBuffer++;
      //接受数据自减
      NumByteToRead--;
    }
    //发送停止信号
        I2C_GenerateSTOP(_EEPROM_I2Cx , ENABLE);
    //使能大应,方便一下I2C输出
    I2C_AcknowledgeConfig(_EEPROM_I2Cx , ENABLE);
    return 1;
}

//--------------------------------------------------------

/**
* @brief void _I2C_EE_Init(void) 
* @param 无
* @retval 正常返回 1 ,不正常返回 0
*/
void _I2C_EE_Init(void){
  I2C_GPIO_Config(); 
  I2C_Mode_Config();
}

/**
* @brief I2C_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite) 
* @param 无
* @retval 正常返回 1 ,不正常返回 0
*/
void I2C_Write_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  u16 i;
  
  printf("I2C_写入数据 \n");
  I2C_EE_WaitEepromStandbyState();
  I2C_EE_PageWrite(pBuffer,WriteAddr, NumByteToWrite);
  for(i=0 ;i<NumByteToWrite ;i++ ){
    printf("%d ", *pBuffer++);
    if(i%16 == 15)    
        printf("\n\r");
  }
  printf("\nI2C_写入数据完成 \n");
  I2C_EE_WaitEepromStandbyState();
   
  for(i=0 ;i<NumByteToWrite ;i++ ){
    printf("%d ", pBuffer[i]);
    if(i%16 == 15)    
        printf("\n\r");
  }
}

/**
* @brief I2C(void I2C_Read_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite))读写测试
* @param 无
* @retval EEP_SIZE
*/
void I2C_Read_fun(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite){
  u16 i;
  
  printf("I2C_数据检测 \n");
  I2C_EE_BufferRead(pBuffer,WriteAddr,NumByteToWrite);
  printf("\nI2C_数据读取完毕 \n");
  for(i=0 ;i<NumByteToWrite ;i++ ){
    printf("%d ", pBuffer[i]);
    if(i%16 == 15)    
        printf("\n\r");
  }
   printf("\n--->I2C_数据检测完成\n");
}
   

7.建立USART传输的 头文件 USART_book.h

代码如下 :

#ifndef  __USART_BOOK_H_
#define  __USART_BOOK_H_

#include "stm32f10x.h"
#include <stdio.h>
#include "stm32f10x_usart.h"
#include "stm32f10x_rcc.h"
 //串口的宏定义  不同的串口挂在的总线和IO不一样
 
 //串口1
#define  _DEBUG_USARTx                  USART1
#define  _DEBUG_USART_CLK               RCC_APB2Periph_USART1
#define  _DEBUG_USART_APBxClkCmd        RCC_APB2PeriphClockCmd
#define  _DEBUG_USART_BAUDRATE          115200
 
// USART  GPIO 引脚定义
#define  _DEBUG_USART_GPIO_CLK          RCC_APB2Periph_GPIOA
#define  _DEBUG_USART_GPIO_APBxCLKCmd   RCC_APB2PeriphClockCmd

#define  _DEBUG_USART_TX_GPIO_PORT      GPIOA
#define  _DEBUG_USART_TX_GPIO_PIN       GPIO_Pin_9
#define  _DEBUG_USART_TX_GPIO_MODE      GPIO_Mode_AF_PP
#define  _DEBUG_USART_RX_GPIO_PORT      GPIOA
#define  _DEBUG_USART_RX_GPIO_PIN       GPIO_Pin_10
#define  _DEBUG_USART_RX_GPIO_MODE      GPIO_Mode_IN_FLOATING

#define  _DEBUG_NVIC_USART_IRQ          USART1_IRQn
#define  _DRBUG_USART_IRQHandler        USART1_IRQHandler
 
void  fn_USART_IO_Config(void); 
void  fn_USART_Config(void); 
void  fn_USART_Init(void);

void  fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch );
void  fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str);
void  Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch);


int fputc (int ch , FILE *f);
int fgetc(FILE *f);
void  _DRBUG_USART_IRQHandler(void);

#endif

8.建立USART传输的C文件 USART_book.c

代码如下 :

#include "USART_book.h"


/**************************************************************
* @brief  
* void fn_LED_Corporate(GPIO_TypeDef*  _GPIO_x , uint16_t  _GPIO_Pin_x , 
*            LED_Corporate_state_t  _LED_Corporate_state_t );
* @param  
* //串口1
*    #define  _DEBUG_NVIC_USART_IRQ               USART1_IRQn
*    #define  _DRBUG_NVIC_USART_IRQHandler        USART1_IRQHandler
* @retval 
*************************************************************/ 
static void NVIC_Configuration(void){
  NVIC_InitTypeDef  NVIC_InitStructure;
  /* 嵌套向量中断控制寄存器组选择*/
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置 USART 为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = _DEBUG_NVIC_USART_IRQ;
  /* 抢断优先级为 1 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级为 1 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置 NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

/**************************************************************
* @brief  
* void fn_LED_Corporate(GPIO_TypeDef*  _GPIO_x , uint16_t  _GPIO_Pin_x , 
*            LED_Corporate_state_t  _LED_Corporate_state_t );
* @param  
* //串口1    
*    // USART  GPIO 引脚定义
*    #define  _DEBUG_USART_GPIO_CLK          RCC_APB2Periph_GPIOA
*    #define  _DEBUG_USART_GPIO_APBxCLKCmd   RCC_APB2PeriphClockCmd
*    
*    #define  _DEBUG_USART_TX_GPIO_PORT      GPIOA
*    #define  _DEBUG_USART_TX_GPIO_PIN       GPIO_Pin_9
*    #define  _DEBUG_USART_TX_GPIO_MODE      GPIO_Mode_AF_PP
*    #define  _DEBUG_USART_RX_GPIO_PORT      GPIOA
*    #define  _DEBUG_USART_RX_GPIO_PIN       GPIO_Pin_10
*    #define  _DEBUG_USART_RX_GPIO_MODE      GPIO_Mode_AF_FLOATING
* @retval 
*************************************************************/ 
void  fn_USART_IO_Config(void){
  GPIO_InitTypeDef    GPIO_InitStructure;
  // 打开串口 GPIO 的时钟
  _DEBUG_USART_GPIO_APBxCLKCmd(_DEBUG_USART_GPIO_CLK , ENABLE);
  
//将USART TX 的GPIO配置为推挽模式
  GPIO_InitStructure.GPIO_Pin = _DEBUG_USART_TX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = _DEBUG_USART_TX_GPIO_MODE;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(_DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStructure);
   //将USART RX 的GPIO配置为浮空输入
  GPIO_InitStructure.GPIO_Pin = _DEBUG_USART_RX_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = _DEBUG_USART_RX_GPIO_MODE;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(_DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure);
}

/**************************************************************
* @brief  
* void fn_LED_Corporate(GPIO_TypeDef*  _GPIO_x , uint16_t  _GPIO_Pin_x , 
*            LED_Corporate_state_t  _LED_Corporate_state_t );
* @param  
* //串口1
*    #define  _DEBUG_USARTx                  USART1
*    #define  _DEBUG_USART_CLK               RCC_APB2Periph_USART1
*    #define  _DEBUG_USART_APBxClkCmd        RCC_APB2PeriphClockCmd
*    #define  _DEBUG_USART_BAUDRATE          115200
* @retval 
*************************************************************/ 
void  fn_USART_Config(void){
  USART_InitTypeDef   USART_InitStructure;
 
  // 打开串口外设的时钟
  _DEBUG_USART_APBxClkCmd(_DEBUG_USART_CLK , ENABLE);
  
  //配置串口的工作参数
  USART_InitStructure.USART_BaudRate = _DEBUG_USART_BAUDRATE;
   //配置波特率
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  // 配置 针数据字长
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  // 配置停止位
  USART_InitStructure.USART_Parity = USART_Parity_No;
  // 配置校验位
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  // 配置硬件流控制
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx ;
  // 配置工作模式,收发一起
  
  USART_Init(_DEBUG_USARTx , &USART_InitStructure);// 完成串口的初始化配置
  
  NVIC_Configuration();// 串口中断优先级配置
  
  USART_ITConfig(_DEBUG_USARTx , USART_IT_RXNE , ENABLE);// 使能串口接收中断
  
  USART_Cmd(_DEBUG_USARTx , ENABLE);// 使能串口
}

/**************************************************************
* @brief  
* void fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch );
* @param  
* //串口1
*    #define  _DEBUG_USARTx                  USART1
*    #define  _DEBUG_USART_CLK               RCC_APB2Periph_USART1
*    #define  _DEBUG_USART_APBxClkCmd        RCC_APB2PeriphClockCmd
*    #define  _DEBUG_USART_BAUDRATE          115200
* @retval 
*************************************************************/ 
void fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch ){
  /*发送一个字节数据到USART*/
  USART_SendData(pUSARTx , ch);
  /*等待发送数据寄存器为空*/
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
}

/**************************************************************
* @brief  
* void fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str);
* @param  
* //串口1
*    #define  _DEBUG_USARTx                  USART1
*    #define  _DEBUG_USART_CLK               RCC_APB2Periph_USART1
*    #define  _DEBUG_USART_APBxClkCmd        RCC_APB2PeriphClockCmd
*    #define  _DEBUG_USART_BAUDRATE          115200
* @retval 
*************************************************************/ 
void fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str){
  unsigned int k = 0;
  do{
    fn_Usart_Send_Byte(pUSARTx,*(str + k++));
    
  }while(*(str + k)!='\0');
  
  /*等待发送完成*/
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC));
}

/**************************************************************
* @brief  
* void Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch);
* @param  
* @retval 
*************************************************************/ 
void Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch){
  uint32_t temp_Half32;
  uint8_t temp_Half=0,i_Half=4; 
  temp_Half32 =ch;
  while(i_Half-->0){
     temp_Half=(temp_Half32 & 0xFF000000)>>24;
     temp_Half32<<=8;
     fn_Usart_Send_Byte(pUSARTx,temp_Half);
  }
  /*等待发送完成*/
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC));
}

/**************************************************************
* @brief  
* void fn_USART_Init(void);
* @param  
* @retval 
*************************************************************/ 
void fn_USART_Init(void){
  fn_USART_IO_Config();
  fn_USART_Config();
}

//须在 MDK 的工程选项把“Use MicroLIB”勾选上,MicoroLIB 是缺省 C 库的备选库,它对
//标准 C 库进行了高度优化使代码更少,占用更少资源。
/**************************************************************
* @brief  
* int fputc (int ch , FILE *f)
* @param  重新定向C库函数Printf 到USART1
* @retval 
*************************************************************/ 
int fputc (int ch , FILE *f){ 
  /*发送一个字节数据到USART*/
  USART_SendData(_DEBUG_USARTx , (uint8_t)ch);
  /*等待发送数据寄存器为空*/
  while(USART_GetFlagStatus(_DEBUG_USARTx,USART_FLAG_TXE)==RESET);
  return (ch);
}

/**************************************************************
* @brief  
* int fgetc(FILE *f);
* @param  重新定向C库函数Printf 到USART1
* @retval 
*************************************************************/  
int fgetc(FILE *f){
  //等待串口1输入数据
  while(USART_GetITStatus(_DEBUG_USARTx,USART_IT_RXNE)==RESET);
  return (int)USART_ReceiveData(_DEBUG_USARTx);
}

/**************************************************************
* @brief  
* void USART1_IRQHandler(void);  中断服务
* @param  
* @retval 
*************************************************************/ 
void _DRBUG_USART_IRQHandler(void){
  uint8_t ucTemp =  0; 
  if(USART_GetITStatus(_DEBUG_USARTx,USART_IT_RXNE)!=RESET){
    ucTemp = USART_ReceiveData(_DEBUG_USARTx);
    USART_SendData(_DEBUG_USARTx ,ucTemp );
  }
}


9.建立DMA传输的 头文件 DMA_book.h

代码如下 :

#ifndef  __DMA_BOOK_H_
#define  __DMA_BOOK_H_

#include "stm32f10x.h"

#define   DMA_CLOCK     RCC_AHBPeriph_DMA1    //DMA  时钟

/******  A   ****************** ROM 到 RAM 的DMA输出 *******************************/
#define   Map_DMA_CHANNEL     DMA1_Channel6    // 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
#define   Map_BUFFER_SIZE     20             // 要发送的数据大小
#define   DMA_FLAG_TC   DMA1_FLAG_TC6         // 传输完成标志
/* 定义 aSRC_Const_Buffer 数组作为 DMA 传输数据源
* const 关键字将 aSRC_Const_Buffer 数组变量定义为常量类型
* 表示数据存储在内部的 FLASH 中*/
extern  const uint32_t  aSRC_Cont_Buffer[Map_BUFFER_SIZE] ;
 /* 定义 DMA 传输目标存储器存储在内部的 SRAM 中*/
extern    uint32_t aDST_Buffer[Map_BUFFER_SIZE];
/*************************************************************************************/

/******** B   **************** USART 到 RAM 的DMA输出 *******************************/
#define   USART_DMA_CHANNEL     DMA1_Channel4         //串口对应的 DMA 请求通道 
#define   USART_Source_ADDR     (USART1_BASE+0x04)    //串口数据的地址
extern    uint32_t              USART_BUFFER_SIZE ;   // 要发送的数据大小
extern    uint32_t*             USART_DMA_Buffer ;
/************************************************************************************/

void      _DMA_Config(DMA_Channel_TypeDef* _DMAy_Channelx , uint32_t _BUFFER_SIZE , uint32_t  _DMA_OutSource_ADDR,  uint32_t  _DMA_InSource_ADDR , uint32_t  _DMA_DIR);
void      _USART_DMA_Config(DMA_Channel_TypeDef* _DMAy_Channelx , uint32_t _BUFFER_SIZE , uint32_t  _DMA_OutSource_ADDR,  uint32_t  _DMA_InSource_ADDR , uint32_t  _DMA_DIR);
uint8_t   _Buffercmp(const uint32_t *pBuffer, uint32_t * pBuffer1 , uint16_t BufferLength);
void      _Buffer_Show(uint32_t * pBuffer , uint16_t BufferLength);
//DMA对内存ROM数据的取出
void _DMA_ROM_TO_RAM(uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR );
//DMA对RAM到USART数据的取出
void _DMA_RAM_TO_USART(uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR );

  
#define   _Map_DMA_Config_    _DMA_Config(Map_DMA_CHANNEL ,Map_BUFFER_SIZE ,aSRC_Cont_Buffer , aDST_Buffer , DMA_DIR_PeripheralSRC)
//  ROM 到 RAM 的DMA输出  的程序初始化   DMA_DIR_PeripheralSRC:为方向外设到内存
#define   _USART_DMA_Config_   _USART_DMA_Config(USART_DMA_CHANNEL ,USART_BUFFER_SIZE ,USART_Source_ADDR , USART_DMA_Buffer , DMA_DIR_PeripheralDST)
//  ROM 到 RAM 的DMA输出  的程序初始化  DMA_DIR_PeripheralDST:为方向外设到内存
#define   _DMA_InnerChange_    _Buffercmp(aSRC_Cont_Buffer , aDST_Buffer, Map_BUFFER_SIZE)
//  ROM 到 RAM 的DMA输出  的数据验证
#define   _RMA_InnerShow_      _Buffer_Show(aDST_Buffer, Map_BUFFER_SIZE)

#endif

10.建立DMA传输的C文件 DMA_book.c

代码如下 :

#include "DMA_book.h"
#include "USART_book.h"
#include "Systick_book.h"

const uint32_t  aSRC_Cont_Buffer  [Map_BUFFER_SIZE]={
   'W','E','L','L',
   'C','O','M','E',
   ' ','S','T','M',
   '3','2',' ','S',
   'T','U','D','Y',
   
};
uint32_t    aDST_Buffer[Map_BUFFER_SIZE] ;
uint32_t*   USART_DMA_Buffer ;
uint32_t  USART_BUFFER_SIZE ;

void _DMA_Config(DMA_Channel_TypeDef* _DMAy_Channelx , uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR , uint32_t  _DMA_DIR){
  DMA_InitTypeDef   DMA_InitStructure ;
  //开启DMA时钟
  RCC_AHBPeriphClockCmd(DMA_CLOCK,ENABLE);
  //源数据缓存地址(外设地址)
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)_DMA_Source_ADDR ; 
  //转换缓存地址地址(内存地址)
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)_DMA_AIM_ADDR;
  
  //方向:外设到存储器(这里的外设是内部的FLASH)DMA_DIR_PeripheralSRC:为方向外设到内存 DMA_DIR_PeripheralDST:为方向外设到内存
  DMA_InitStructure.DMA_DIR = _DMA_DIR ;
  //传输大小
  DMA_InitStructure.DMA_BufferSize = _BUFFER_SIZE;
  //外设(内部的FLASH)地址递增
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  //内存地址递增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  //外设数据单位
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  //内存数据单位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  //DMA模式,一次或者循环模式
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //优先级:高
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  //使能内存到内存的传输
  DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
  //配置DMA通道
  DMA_Init(_DMAy_Channelx , &DMA_InitStructure);
  //使能DMA
  DMA_Cmd(_DMAy_Channelx , ENABLE);
}

void _USART_DMA_Config(DMA_Channel_TypeDef* _DMAy_Channelx , uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR , uint32_t  _DMA_DIR){
  DMA_InitTypeDef   DMA_InitStructure ;
  //开启DMA时钟
  RCC_AHBPeriphClockCmd(DMA_CLOCK,ENABLE);  
  //源数据缓存地址(外设地址)
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)_DMA_Source_ADDR ; 
  //转换缓存地址地址(内存地址)
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)_DMA_AIM_ADDR;
  
  //方向:外设到存储器(这里的外设是内部的FLASH)DMA_DIR_PeripheralSRC:为方向外设到内存 DMA_DIR_PeripheralDST:为方向外设到内存
  DMA_InitStructure.DMA_DIR = _DMA_DIR ;
  //传输大小
  DMA_InitStructure.DMA_BufferSize = _BUFFER_SIZE;
  //外设(内部的FLASH)地址递增
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  //内存地址递增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  //外设数据单位
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//DMA_PeripheralDataSize_Byte; //注意这里需要根据数据类型经行修改
  //内存数据单位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;//DMA_MemoryDataSize_Byte; //注意这里需要根据数据类型经行修改
  //DMA模式,一次或者循环模式
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //优先级:高
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
  //使能内存到内存的传输
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  //配置DMA通道
  DMA_Init(_DMAy_Channelx , &DMA_InitStructure);
  //使能DMA
  DMA_Cmd(_DMAy_Channelx , ENABLE);
}
 
///源数据与目标地址数据对比
uint8_t  _Buffercmp(const uint32_t *pBuffer, uint32_t * pBuffer1 , uint16_t BufferLength){
  /*数据长度递减*/
  while(BufferLength--){
//  Usart_SendHalf_32_Word(_DEBUG_USARTx,*pBuffer);  
//  Usart_SendHalf_32_Word(_DEBUG_USARTx,*pBuffer1);
    /*判断两个数据源是否相等*/
    if(*pBuffer != *pBuffer1){
      /* 对应数据源不相等马上退出函数,并返回 0 */
      return 0;
    }
    /* 递增两个数据源的地址指针 */
    pBuffer++;
    pBuffer1++;
  }
  /* 完成判断并且对应数据相对 */
  return 1;
}


//对RAM数据进行展示
void  _Buffer_Show(uint32_t * pBuffer , uint16_t BufferLength){
  /*数据长度递减*/
  while(BufferLength--){
    Usart_SendHalf_32_Word(_DEBUG_USARTx,*pBuffer++);  
  }
}

//DMA对内存ROM数据的取出
void _DMA_ROM_TO_RAM(uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR ){
  //----------------------------------------------------------------
  printf("开始 ROM内存到RAM内存的DMA操作 \n");
  //内存到内存DMA初始化
  _DMA_Config(Map_DMA_CHANNEL ,_BUFFER_SIZE ,_DMA_Source_ADDR , _DMA_AIM_ADDR , DMA_DIR_PeripheralSRC);       
  while(DMA_GetFlagStatus(DMA_FLAG_TC) == RESET);  //判断DMA传输结果是否正确          
  if(_DMA_InnerChange_== 0 ){
      printf("ROM内存到DMA操作异常 \n");  
  }else{
      printf("ROM内存到DMA操作正常 \n");      
  }  
  _RMA_InnerShow_;
}

//DMA对RAM到USART数据的取出
void _DMA_RAM_TO_USART(uint32_t _BUFFER_SIZE , uint32_t  _DMA_Source_ADDR,  uint32_t  _DMA_AIM_ADDR ){
  // 开始 USART内存到RAM内存的DMA操作 
  printf("\n开始 ROM到USART的传送初始化\n"); 
  USART_BUFFER_SIZE = _BUFFER_SIZE;
  USART_DMA_Buffer = _DMA_AIM_ADDR;
  //内存到USART DMA初始化
  _USART_DMA_Config(USART_DMA_CHANNEL ,USART_BUFFER_SIZE ,_DMA_Source_ADDR , USART_DMA_Buffer , DMA_DIR_PeripheralDST);
  USART_DMACmd(_DEBUG_USARTx , USART_DMAReq_Tx , ENABLE);  //串口DMA使能
  /*USART_DMACmd 函数用于控制 USART 的 DMA 请求的启动和关闭。它接收三个参
  数,第一个参数用于设置串口外设,可以是 USART1/2/3 和 UART4/5 这 5 个参数可选,第
  二个参数设置串口的具体 DMA 请求,有串口发送请求 USART_DMAReq_Tx 和接收请求
  USART_DMAReq_Rx 可选,第三个参数用于设置启动请求 ENABLE 或者关闭请求*/
  
  fn_Systick_Delay(250,_Systick_ms);   //DMA 传输进程中进行LED输出闪烁  
  while(USART_GetFlagStatus(_DEBUG_USARTx,USART_FLAG_TXE)==RESET);
  printf("\rROM内存到USART外设的DMA操作完毕\n");//这个函数需要Delay 一段时间才可以用
}
 
//----------------------------------------------------------------


11.建立EXIT的 头文件 Exit_book.h

代码如下 :

#ifndef  __EXIT_BOOK_H_
#define  __EXIT_BOOK_H_

#include "stm32f10x.h"

#define  _KEY_EXTI_IN_GPIO_Port      GPIOA
#define  _KEY_EXTI_IN_GPIO_Pin       GPIO_Pin_0
#define  _EXTI_IN_GPIO_PortSource     GPIO_PortSourceGPIOA
#define  _EXTI_IN_GPIO_PinSource      GPIO_PinSource0
#define  _EXTI_IN_EXTI_Line           EXTI_Line0 
#define  _EXTI_IN_EXTI_Trigger        EXTI_Trigger_Rising
#define  _EXTI_IN_GPIO_Clock          RCC_APB2Periph_AFIO
#define  _EXTI_IN_EXTI_Mode           EXTI_Mode_Interrupt
#define  _EXTI_IN_EXTI_LineCmd        ENABLE

#define  _NVIC_IN_EXTI_IRQChannel     EXTI0_IRQn
#define  _NVIC_IN_EXTI_IRQChannelCmd  ENABLE

#define  _KEY2_EXTI_IN_GPIO_Port     GPIOC
#define  _KEY2_EXTI_IN_GPIO_Pin      GPIO_Pin_13
#define  _EXTI_IN2_GPIO_PortSource    GPIO_PortSourceGPIOC
#define  _EXTI_IN2_GPIO_PinSource     GPIO_PinSource13
#define  _EXTI_IN2_EXTI_Line          EXTI_Line13
#define  _EXTI_IN2_EXTI_Trigger       EXTI_Trigger_Falling
#define  _EXTI_IN2_GPIO_Clock         RCC_APB2Periph_AFIO
#define  _EXTI_IN2_EXTI_Mode          EXTI_Mode_Interrupt
#define  _EXTI_IN2_EXTI_LineCmd       ENABLE

#define  _NVIC_IN2_EXTI_IRQChannel    EXTI15_10_IRQn
#define  _NVIC_IN2_EXTI_IRQChannelCmd  ENABLE

void  fn_EXTI_GPIO_Config(void);
void  fn_NVIC_Config(void);
void  EXTI0_IRQHandler(void);

#endif

12.建立EXIT的C文件 Exit_book.c

代码如下 :

#include "Exit_book.h"
#include "Led_book.h"

/**************************************************************
* @brief  
* void  fn_EXTI_GPIO_Config(void)
* @param  
*    
*   #define  _KEY_EXTI_IN_GPIO_Port      GPIOA
*   #define  _KEY_EXTI_IN_GPIO_Pin       GPIO_Pin_0
*   #define  _EXTI_IN_GPIO_PortSource     GPIO_PortSourceGPIOA
*   #define  _EXTI_IN_GPIO_PinSource      GPIO_PinSource0
*   #define  _EXTI_IN_EXTI_Line           EXTI_Line0 
*   #define  _EXTI_IN_EXTI_Trigger        EXTI_Trigger_Rising
*   #define  _EXTI_IN_GPIO_Clock          RCC_APB2Periph_AFIO
*   #define  _EXTI_IN_EXTI_Mode           EXTI_Mode_Interrupt
*   #define  _EXTI_IN_EXTI_LineCmd        ENABLE
*   
*   #define  _KEY2_EXTI_IN_GPIO_Port     GPIOC
*   #define  _KEY2_EXTI_IN_GPIO_Pin      GPIO_Pin_13
*   #define  _EXTI_IN2_GPIO_PortSource    GPIO_PortSourceGPIOC
*   #define  _EXTI_IN2_GPIO_PinSource     GPIO_PinSource13
*   #define  _EXTI_IN2_EXTI_Line          EXTI_Line13
*   #define  _EXTI_IN2_EXTI_Trigger       EXTI_Trigger_Falling   
*   #define  _EXTI_IN2_GPIO_Clock         RCC_APB2Periph_AFIO
*   #define  _EXTI_IN2_EXTI_Mode          EXTI_Mode_Interrupt
*   #define  _EXTI_IN2_EXTI_LineCmd       ENABLE
* @retval 
*************************************************************/ 
void  fn_EXTI_GPIO_Config(void){
  EXTI_InitTypeDef   EXIT_InitStruck;
  RCC_APB2PeriphClockCmd(_EXTI_IN_GPIO_Clock , ENABLE);  
  //注意:我们除了开 GPIO 的端口时钟外,我们还打开了 AFIO 的时钟
  GPIO_EXTILineConfig(_EXTI_IN_GPIO_PortSource | _EXTI_IN2_GPIO_PortSource , _EXTI_IN_GPIO_PinSource | _EXTI_IN2_GPIO_PinSource);
  /* 选择 EXTI 的信号源 */
  // GPIO_EXTILineConfig 函数用来指定中断/事件线的输入源,它实际是设定外部中断配
  // 置寄存器的 AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定 GPIO 端口源,第
  // 二个参数为选择对应 GPIO 引脚源编号。
  
  
  EXIT_InitStruck.EXTI_Line = _EXTI_IN_EXTI_Line ; /* 选择 EXTI 的信号源 */
  EXIT_InitStruck.EXTI_Mode = _EXTI_IN_EXTI_Mode;   /* EXTI 为中断模式 */
  EXIT_InitStruck.EXTI_Trigger = _EXTI_IN_EXTI_Trigger ; /* 上升沿中断 */
  EXIT_InitStruck.EXTI_LineCmd = _EXTI_IN_EXTI_LineCmd; /* 使能中断 */
  EXTI_Init(&EXIT_InitStruck);
  //  EXTI初始化配置的变量
  //  fn_NVIC_Config();
  //  调用 NVIC_Configuration函数完成对按键 1、按键 2 优先级配置并使能中断通道
  
  EXIT_InitStruck.EXTI_Line = _EXTI_IN2_EXTI_Line; /* 选择 EXTI 的信号源 */
  EXIT_InitStruck.EXTI_Mode = _EXTI_IN2_EXTI_Mode;   /* EXTI 为中断模式 */
  EXIT_InitStruck.EXTI_Trigger = _EXTI_IN2_EXTI_Trigger; /* 下降沿中断 */
  EXIT_InitStruck.EXTI_LineCmd = _EXTI_IN_EXTI_LineCmd;/* 使能中断 */
  EXTI_Init(&EXIT_InitStruck);
  
  fn_NVIC_Config();
}

/**************************************************************
* @brief  
* void  fn_NVIC_Config(void)
* @param  
*   #define  _NVIC_IN_EXTI_IRQChannel     EXTI0_IRQn
*   #define  _NVIC_IN_EXTI_IRQChannelCmd  ENABLE
*   #define  _NVIC_IN2_EXTI_IRQChannel    EXTI15_10_IRQn
*   #define  _NVIC_IN2_EXTI_IRQChannelCmd  ENABLE
* @retval 
*************************************************************/ 
void  fn_NVIC_Config(void){
  NVIC_InitTypeDef NVIC_InitStruct;
  /* 配置 NVIC 为优先级组 1 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中断源:  */
  NVIC_InitStruct.NVIC_IRQChannel = _NVIC_IN_EXTI_IRQChannel; //EXTI0_IRQn;
  /* 配置抢占优先级:1 */
  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
  /* 配置子优先级:1 */
  NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断通道 */
  NVIC_InitStruct.NVIC_IRQChannelCmd = _NVIC_IN_EXTI_IRQChannelCmd; //ENABLE
  NVIC_Init(&NVIC_InitStruct);
  
  /* 配置中断源:  */
  NVIC_InitStruct.NVIC_IRQChannel = _NVIC_IN2_EXTI_IRQChannel; //EXTI0_IRQn;
  NVIC_Init(&NVIC_InitStruct);
}

/**************************************************************
* @brief  
* void  fn_NVIC_Config(void)
* @param  
*   #define   _KEY_EXTI_IN_GPIO_Port      GPIOA
*   #define   _KEY_EXTI_IN_GPIO_Pin       GPIO_Pin_0
* @retval 
*************************************************************/ 
void EXTI0_IRQHandler(void){
//  EXTI_GetITStatus 函数用来获取 EXTI 的中断标志位状态,如果 EXTI 线有中断发生函
//数返回“SET”否则返回“RESET”。实际上,EXTI_GetITStatus 函数是通过读取
//EXTI_PR寄存器值来判断 EXTI线状态的。
  if(EXTI_GetITStatus(_EXTI_IN_EXTI_Line)!= RESET){
    if(GPIO_ReadInputDataBit(_KEY_EXTI_IN_GPIO_Port, _KEY_EXTI_IN_GPIO_Pin)==1){
      __LED_Change__;
    }
  }
  EXTI_ClearITPendingBit(_EXTI_IN_EXTI_Line);  // 重要的清除中断标志位 
}


void EXTI15_10_IRQHandler(void){
 if(EXTI_GetITStatus(_EXTI_IN2_EXTI_Line)!= RESET){
    if(GPIO_ReadInputDataBit(_KEY2_EXTI_IN_GPIO_Port, _KEY2_EXTI_IN_GPIO_Pin)==0){
      __LED_Change__;
    }
  }
  EXTI_ClearITPendingBit(_EXTI_IN2_EXTI_Line);  // 重要的清除中断标志位 
}
 

13.建立Key传输的 头文件 Key_book.h

代码如下 :

#ifndef  __KEY_BOOK_H_
#define  __KEY_BOOK_H_


#include "stm32f10x.h"
#include "Led_book.h"

#define   KEY_IN_GPIO_Port      GPIOA
#define   KEY_IN_GPIO_Clock     RCC_APB2Periph_GPIOA
#define   KEY_IN_GPIO_Pin       GPIO_Pin_0
#define   KEY_IN_GPIO_Pin_Bit   0
#define   Key_IN_GPIO_Modle     GPIO_Mode_IN_FLOATING   //浮空输入


#define   KEY2_IN_GPIO_Port      GPIOC
#define   KEY2_IN_GPIO_Clock     RCC_APB2Periph_GPIOC
#define   KEY2_IN_GPIO_Pin       GPIO_Pin_13
#define   KEY2_IN_GPIO_Pin_Bit   13
#define   Key2_IN_GPIO_Modle     GPIO_Mode_IN_FLOATING   //浮空输入


typedef union {
  struct{
    unsigned char BIT0:1;unsigned char BIT1:1;unsigned char BIT2:1;unsigned char BIT3:1;
    unsigned char BIT4:1;unsigned char BIT5:1;unsigned char BIT6:1;unsigned char BIT7:1;
    //unsigned char BIT8:1;unsigned char BIT9:1;unsigned char BIT10:1;unsigned char BIT11:1;
    //unsigned char BIT12:1;unsigned char BIT13:1;unsigned char BIT14:1;unsigned char BIT15:1;
  }DATA_BIT;
  uint8_t DATA_BYTE;
}Per_key_type;

extern volatile  Per_key_type key_flag;
  #define bkey_10ms         key_flag.DATA_BIT.BIT0
  #define bkey_judge        key_flag.DATA_BIT.BIT1
  #define bkey_judge_long   key_flag.DATA_BIT.BIT2
  #define bkey_Effect       key_flag.DATA_BIT.BIT3
  #define bkey_LongEffect   key_flag.DATA_BIT.BIT4
  #define bkey_Effect_Lose  key_flag.DATA_BIT.BIT5
  #define bkey_Effect_LLose key_flag.DATA_BIT.BIT6
void  fn_Key_GPIO_Config( GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock , uint16_t _GPIO_Pin_x , GPIOMode_TypeDef  _GPIOMode_TypeDef );
void  fn_Key_Init(void);
void  fn_key_judge(void);
void  fn_key_Effect(void);  
void  fn_key_Check(void);
#endif

14.建立Key的C文件 Key_book.c

代码如下 :


#include  "Key_book.h"
 

volatile  Per_key_type key_flag;

/**************************************************************
* @brief  
* void  fn_Key_GPIO_Config( GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock , 
*                  uint16_t _GPIO_Pin_x , GPIOMode_TypeDef  _GPIOMode_TypeDef );
* @param  
*     #define   KEY_IN_GPIO_Port      GPIOA
*     #define   KEY_IN_GPIO_Clock     RCC_APB2Periph_GPIOA
*     #define   KEY_IN_GPIO_Pin       GPIO_Pin_0
*     #define   KEY_IN_GPIO_Pin_Bit   0
*     #define   Key_IN_GPIO_Modle     GPIO_Mode_IN_FLOATING   //浮空输入
*     
*     #define   KEY2_IN_GPIO_Port      GPIOC
*     #define   KEY2_IN_GPIO_Clock     RCC_APB2Periph_GPIOC
*     #define   KEY2_IN_GPIO_Pin       GPIO_Pin_13
*     #define   KEY2_IN_GPIO_Pin_Bit   13
*     #define   Key2_IN_GPIO_Modle     GPIO_Mode_IN_FLOATING   //浮空输入
* @retval 
*************************************************************/ 
void  fn_Key_GPIO_Config( GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock , uint16_t _GPIO_Pin_x , GPIOMode_TypeDef  _GPIOMode_TypeDef ){
    GPIO_InitTypeDef  GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = _GPIOMode_TypeDef;
    GPIO_InitStruct.GPIO_Pin = _GPIO_Pin_x;
    RCC_APB2PeriphClockCmd(_GPIO_Clock,ENABLE);
    GPIO_Init(_GPIO_x , &GPIO_InitStruct);  
}

/**************************************************************
* @brief  
* void fn_Key_Init(void);
* @param  
* @retval 
*************************************************************/ 
void  fn_Key_Init(void){
    fn_Key_GPIO_Config(KEY_IN_GPIO_Port,KEY_IN_GPIO_Clock,KEY_IN_GPIO_Pin,Key_IN_GPIO_Modle);
    fn_Key_GPIO_Config(KEY2_IN_GPIO_Port,KEY2_IN_GPIO_Clock,KEY2_IN_GPIO_Pin,Key2_IN_GPIO_Modle);
}

/************************************************************
* @brief  
* void  fn_key_judge(void);
* @param  
* @retval 
**************************************************************/ 
#define  _LONG_key  30
static uint16_t count_key ;
void  fn_key_judge(void){
   
   if(!bkey_10ms){return;}
   bkey_10ms = 0;
   if(GPIO_ReadInputDataBit(KEY_IN_GPIO_Port,KEY_IN_GPIO_Pin)){
     if(count_key++<3){return;}
     if(!bkey_judge){
       bkey_judge = 1;
       bkey_Effect = 1; 
     }else{
       if(count_key>_LONG_key){
          bkey_judge_long = 1;
          bkey_LongEffect = 1;
       }
     }
   }
   else{
     count_key = 0;
     if(bkey_judge){
        bkey_judge = 0;
        if(bkey_judge_long){
            bkey_judge_long = 0;
            bkey_Effect_LLose = 1;
        }else{
            bkey_judge_long = 0;
            bkey_Effect_Lose = 1;
        }
     }else{
        bkey_judge = 0;         
     }
  }
}

/************************************************************
* @brief  
* void fn_key_Effect(void);
* @param  
* @retval 
*************************************************************/ 
void  fn_key_Effect(void){
  if(bkey_Effect){
    bkey_Effect = 0;
    fn_LED_Corporate(LED_OUT_GPIO_Port,LED_OUT_GPIO_Pin,LED_Corporate_Toggle);
  }
}

/**************************************************************
* @brief  
* void fn_key_Check(void);
* @param  
* @retval 
*************************************************************/ 
void fn_key_Check(void){
  fn_key_judge();
  fn_key_Effect();
}


15.建立LED 的头文件 Led_book.h

代码如下 :

#ifndef  __LED_BOOK_H_
#define  __LED_BOOK_H_

#include "stm32f10x.h"
 

#define   LED_OUT_GPIO_Port     GPIOB                 //GPIO Point
#define   LED_OUT_GPIO_Clock    RCC_APB2Periph_GPIOB  //GPIO clock
#define   LED_OUT_GPIO_Pin      GPIO_Pin_5             
#define   LED_OUT_GPIO_Pin_Bit  5
#define   LED_OUT_GPIO_Modle    GPIO_Mode_Out_PP

#define   LED_R_OUT_GPIO_Pin      GPIO_Pin_5             
#define   LED_G_OUT_GPIO_Pin     GPIO_Pin_0             
#define   LED_B_OUT_GPIO_Pin     GPIO_Pin_1             
typedef enum {
		LED_Corporate_On = 1,
		LED_Corporate_OFF = 2,
		LED_Corporate_Toggle = 3, 
} LED_Corporate_state_t;

void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,\
          uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef);
void fn_Led_Init(void);
void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x , \
          LED_Corporate_state_t _LED_Corporate_state_t );
  
void  fn_LED_ALL_OFF(void);
#define __LED_Change__  fn_LED_Corporate(LED_OUT_GPIO_Port,LED_OUT_GPIO_Pin,LED_Corporate_Toggle)

#define __R_OUT__  GPIO_ResetBits(LED_OUT_GPIO_Port,LED_R_OUT_GPIO_Pin)
#define __G_OUT__  GPIO_ResetBits(LED_OUT_GPIO_Port,LED_G_OUT_GPIO_Pin)
#define __B_OUT__  GPIO_ResetBits(LED_OUT_GPIO_Port,LED_B_OUT_GPIO_Pin)

#endif


16.建立LED 的 文件 Led_book.c

代码如下 :

#include "Led_book.h"

/**************************************************************
* @brief  
* void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,
*             uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef);
* @param  
* @retval 
*************************************************************/ 
#define LED_GPIO_Speed GPIO_Speed_10MHz 
void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef){
  GPIO_InitTypeDef  GPIO_InitStruct;
  GPIO_InitStruct.GPIO_Mode = _GPIOMode_TypeDef;
  GPIO_InitStruct.GPIO_Pin = _GPIO_Pin_x;
  GPIO_InitStruct.GPIO_Speed = LED_GPIO_Speed;
  RCC_APB2PeriphClockCmd(_GPIO_Clock ,ENABLE); 
  GPIO_Init(_GPIO_x , &GPIO_InitStruct) ; 
  GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
}

/**************************************************************
* @brief  
* void fn_Led_Init(void);
* @param  
* @retval 
*************************************************************/ 
void fn_Led_Init(void){
  fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
  fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_R_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
  fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_G_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
  fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_B_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
  fn_LED_ALL_OFF();
}

/**************************************************************
* @brief  
* void fn_LED_Corporate(GPIO_TypeDef*  _GPIO_x , uint16_t  _GPIO_Pin_x , 
*            LED_Corporate_state_t  _LED_Corporate_state_t );
* @param  
* @retval 
*************************************************************/ 
void fn_LED_Corporate(GPIO_TypeDef*  _GPIO_x , uint16_t  _GPIO_Pin_x , LED_Corporate_state_t  _LED_Corporate_state_t ){
  switch(_LED_Corporate_state_t){
    case  LED_Corporate_On :
      GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
      break;
		case  LED_Corporate_OFF:
      GPIO_ResetBits(_GPIO_x,_GPIO_Pin_x);
      break;
		case  LED_Corporate_Toggle:
      GPIO_ReadOutputDataBit(_GPIO_x,_GPIO_Pin_x)?GPIO_ResetBits(_GPIO_x,_GPIO_Pin_x):GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
      break;    
  }
}

void  fn_LED_ALL_OFF(void){
  GPIO_SetBits(LED_OUT_GPIO_Port,LED_R_OUT_GPIO_Pin);
  GPIO_SetBits(LED_OUT_GPIO_Port,LED_G_OUT_GPIO_Pin);
  GPIO_SetBits(LED_OUT_GPIO_Port,LED_B_OUT_GPIO_Pin);
}

//practice
//fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
// while(1){
//  delay(10000);
//  fn_LED_Corporate(LED_OUT_GPIO_Port,LED_OUT_GPIO_Pin,LED_Corporate_Toggle);		 
// }	 


17.建立 Systick传输的 头文件 Systick_book.h

代码如下 :

#ifndef  __SYSTIC_BOOK_H_
#define  __SYSTIC_BOOK_H_

#include "stm32f10x.h"
#include  "Key_book.h"
 
typedef enum {
		_Systick_us = 1,
		_Systick_ms = 2,
		_Systick_s = 3, 
} Systick_time_state_t;

void fn_Systick_Delay(uint32_t  _Delay_time , Systick_time_state_t  _Systick_time_state_t);
void fn_Systick_Delay_Handler_set(uint32_t  _Delay_ms , Systick_time_state_t  _Systick_time_state_t);
void fn_SysTick_delay_decrement(void);
void SysTick_Handler(void);


#define  __Systick_Delay_Handler_set__      fn_Systick_Delay_Handler_set(10,_Systick_ms)
#endif


18.建立 Systick的C文件 Systick_book.c

代码如下 :

#include "Systick_book.h"

/************************************************************
* @brief  
* void fn_Systick_Delay(uint32_t  _Delay_time , \
Systick_time_state_t  _Systick_time_state_t){
* @param  
* @retval 
*************************************************************/ 
void fn_Systick_Delay(uint32_t  _Delay_time , Systick_time_state_t  _Systick_time_state_t){
   uint32_t  i;
   if(_Systick_time_state_t == _Systick_us){SysTick_Config(SystemCoreClock/1000000);}
   if(_Systick_time_state_t == _Systick_ms){
    SysTick_Config(SystemCoreClock/1000);
   }  
   else{SysTick_Config(SystemCoreClock);}      
   for( i=0;i<_Delay_time ; i++){
    while(!((SysTick->CTRL)&(1<<16)));
   }
   SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;

}

/************************************************************
* @brief  
* void fn_Systick_Delay_Handler_set(uint32_t  _Delay_ms , \
*       Systick_time_state_t  _Systick_time_state_t){
* @param  
* @retval 
*************************************************************/ 
static uint32_t _SysTick_delay  = 0 ;
void fn_Systick_Delay_Handler_set(uint32_t  _Delay_ms , Systick_time_state_t  _Systick_time_state_t){
  if(_Systick_time_state_t == _Systick_us){SysTick_Config(SystemCoreClock/1000000);}
  if(_Systick_time_state_t == _Systick_ms){
      SysTick_Config(SystemCoreClock/1000);
  }  
  else{SysTick_Config(SystemCoreClock);}      
  _SysTick_delay = _Delay_ms ;
}

/************************************************************
* @brief  
* void fn_SysTick_delay_decrement(void)
* @param  
* @retval 
*************************************************************/ 
static uint32_t SysTick_delay = 0 ;
void fn_SysTick_delay_decrement(void){
  if(SysTick_delay++ > _SysTick_delay){
    SysTick_delay = 0;
    bkey_10ms = 1;
  }
}

/************************************************************
* @brief  
* void SysTick_Handler(void)
* @param  
* @retval 
*************************************************************/ 
void SysTick_Handler(void){
  fn_SysTick_delay_decrement();
}

19.建立 头文件函数 头文件 PROJ_book.h

代码如下 :

#ifndef __PROJ_BOOK_H__
#define __PROJ_BOOK_H__
 
#include "stm32f10x.h"
#include "Led_book.h"
#include "Key_book.h"	 
#include "RCC_book.h"
#include "Systick_book.h"
#include "Exit_book.h"
#include "USART_book.h"
#include "DMA_book.h"
#include "I2C_book.h"
#include "I2C_soft_book.h"

#endif

在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 17:13:27  更:2021-07-31 17:13:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 10:06:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计