固件库模板

GPIO 输入输出

 GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  // 打开 GPIOB 的时钟GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;       // 推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;              // PB0 端口引脚GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;      // 50MHZ速度GPIO_Init(GPIOB, &GPIO_InitStruct);                   // 初始化GPIO_SetBits(GPIOB, GPIO_Pin_0);  // 高电平GPIO_ResetBits(GPIOB, GPIO_Pin_0);    // 低电平

按键

以野火STM32F103ZET6为例,按键有硬件消抖功能,故不用延时

//按下高电平
void KEY_GPIO_Config()
{GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;      // 浮空输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 ;                 // PA0// 按键输入没有规定速度GPIO_Init(GPIOA, &GPIO_InitStruct);
}// 按键判断函数 Key(GPIOA, GPIO_Pin_0)
void Key(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1){·······//松手检测while(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1);}else{······}
}

位带操作

// sys.h

#ifndef __SYS_H
#define __SYS_H#include "stm32f10x.h"//定义GPIO结构体位域
typedef struct
{volatile unsigned short bit0 : 1;volatile unsigned short bit1 : 1;volatile unsigned short bit2 : 1;volatile unsigned short bit3 : 1;volatile unsigned short bit4 : 1;volatile unsigned short bit5 : 1;volatile unsigned short bit6 : 1;volatile unsigned short bit7 : 1;volatile unsigned short bit8 : 1;volatile unsigned short bit9 : 1;volatile unsigned short bit10 : 1;volatile unsigned short bit11 : 1;volatile unsigned short bit12 : 1;volatile unsigned short bit13 : 1;volatile unsigned short bit14 : 1;volatile unsigned short bit15 : 1;} GPIO_Bit_TypeDef;#define PORTA_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOA->ODR)))
#define PORTA_IN     ((GPIO_Bit_TypeDef *)(&(GPIOA->IDR)))
#define PORTB_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOB->ODR)))
#define PORTB_IN     ((GPIO_Bit_TypeDef *)(&(GPIOB->IDR)))
#define PORTC_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOC->ODR)))
#define PORTC_IN     ((GPIO_Bit_TypeDef *)(&(GPIOC->IDR)))
#define PORTD_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOD->ODR)))
#define PORTD_IN     ((GPIO_Bit_TypeDef *)(&(GPIOD->IDR)))
#define PORTE_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOE->ODR)))
#define PORTE_IN     ((GPIO_Bit_TypeDef *)(&(GPIOE->IDR)))
#define PORTF_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOF->ODR)))
#define PORTF_IN     ((GPIO_Bit_TypeDef *)(&(GPIOF->IDR)))
#define PORTG_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOG->ODR)))
#define PORTG_IN     ((GPIO_Bit_TypeDef *)(&(GPIOG->IDR)))#define PAout(n) (PORTA_OUT->bit##n)
#define PAin(n)  (PORTA_IN->bit##n)
#define PBout(n) (PORTB_OUT->bit##n)
#define PBin(n)  (PORTB_IN->bit##n)
#define PCout(n) (PORTC_OUT->bit##n)
#define PCin(n)  (PORTC_IN->bit##n)
#define PDout(n) (PORTD_OUT->bit##n)
#define PDin(n)  (PORTD_IN->bit##n)
#define PEout(n) (PORTE_OUT->bit##n)
#define PEin(n)  (PORTE_IN->bit##n)
#define PFout(n) (PORTF_OUT->bit##n)
#define PFin(n)  (PORTF_IN->bit##n)
#define PGout(n) (PORTG_OUT->bit##n)
#define PGin(n)  (PORTG_IN->bit##n)#define CLK_PA        RCC_APB2Periph_GPIOA
#define CLK_PB      RCC_APB2Periph_GPIOB
#define CLK_PC      RCC_APB2Periph_GPIOC
#define CLK_PD      RCC_APB2Periph_GPIOD
#define CLK_PE      RCC_APB2Periph_GPIOE
#define CLK_PF      RCC_APB2Periph_GPIOF
#define CLK_PG      RCC_APB2Periph_GPIOG#define HIGH        1
#define LOW         0void Pin_out(uint32_t Periph_CLK, uint16_t GPIO_Pin, uint16_t elec_level);#endif

// sys.c

#include "sys.h"void Pin_out(uint32_t Periph_CLK, uint16_t GPIO_Pin, uint16_t elec_level)
{GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(Periph_CLK, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;if(Periph_CLK == CLK_PA){GPIO_Init(GPIOA, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOA, GPIO_Pin);}else{GPIO_ResetBits(GPIOA, GPIO_Pin);}   }else if(Periph_CLK == CLK_PB){GPIO_Init(GPIOB, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOB, GPIO_Pin);}else{GPIO_ResetBits(GPIOB, GPIO_Pin);}}else if(Periph_CLK == CLK_PC){GPIO_Init(GPIOC, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOC, GPIO_Pin);}else{GPIO_ResetBits(GPIOC, GPIO_Pin);}}else if(Periph_CLK == CLK_PD){GPIO_Init(GPIOD, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOD, GPIO_Pin);}else{GPIO_ResetBits(GPIOD, GPIO_Pin);}}else if(Periph_CLK == CLK_PE){GPIO_Init(GPIOE, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOE, GPIO_Pin);}else{GPIO_ResetBits(GPIOE, GPIO_Pin);}}else if(Periph_CLK == CLK_PF){GPIO_Init(GPIOF, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOF, GPIO_Pin);}else{GPIO_ResetBits(GPIOF, GPIO_Pin);}}else if(Periph_CLK == CLK_PG){GPIO_Init(GPIOG, &GPIO_InitStruct);if(elec_level){GPIO_SetBits(GPIOG, GPIO_Pin);}else{GPIO_ResetBits(GPIOG, GPIO_Pin);}}
}//************************************************************

位带操作例题:

main.c

#include "stm32f10x.h"
#include "sys.h"int main()
{GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 打开 GPIOB 的时钟GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;           // 推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;                  // PB0 端口引脚GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;          // 50MHZ速度11GPIO_Init(GPIOB, &GPIO_InitStruct);                     // 初始化PBout(0) = 1;    // 初始化端口之后才可以用//Pin_out(CLK_PB, GPIO_Pin_0, HIGH);  // 即一个函数直接 PB0 输出高电平return 0;
}

系统时钟

// BSP_RCC_clkconfig.h

#ifndef __BSP_RCC_CLKCONFIG_H
#define __BSP_RCC_CLKCONFIG_H#include "stm32f10x.h"void HSE_SetSysClk(uint32_t RCC_PLLMul_x);
void MCO_GPIO_Config();#endif

// BSP_RCC_clkconfig.c

 #include "BSP_RCC_clkconfig.h"void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
{ErrorStatus HSEStatus;   //变量初始化定义//把RCC寄存器复位成复位值RCC_DeInit();//打开外部高速时钟晶振HSERCC_HSEConfig(RCC_HSE_ON);    //使能外部的高速时钟//等待外部高速时钟晶振工作,因为HSE是8M的无源晶振,配备了谐振电容,启动需要时间HSEStatus = RCC_WaitForHSEStartUp();  //   ^_^ 等~if(HSEStatus == SUCCESS)    //HSE启动成功    ^o^{FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);  //使能预取指//SYSCLK 周期与闪存访问时间的比例设置,这里统一设置为 2FLASH_SetLatency(FLASH_Latency_2);/*设置为 2 的时候,SYSCLK低于48M也可以工作,如果设置成0或1的时候,如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了*///AHB预分频因子设置为1分频RCC_HCLKConfig(RCC_SYSCLK_Div1);    //HCLK = SYSCLK     72MHZ//APB1预分频因子设置为2分频RCC_PCLK1Config(RCC_HCLK_Div2);      //PCLK1 = HCLK/2   36MHZ//APB2预分频因子设置为1分频RCC_PCLK2Config(RCC_HCLK_Div1);       //PCLK2 =  HCLK        72MHZ//配置锁相环   PLLCLK = HSE * RCC_PLLMul_xRCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x); //倍频//使能PLLCLKRCC_PLLCmd(ENABLE);   //使用PLL锁相环//等待PLL稳定while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);//PLL稳定之后,选择系统时钟RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);   //选择PLL作为系统时钟while(RCC_GetSYSCLKSource() != 0x08);     //等待使用PLL作为系统时钟源/**     - 返回值为下列其中之一:*     - 0x00: HSI用作系统时钟*     - 0x04: HSE用作系统时钟*     - 0x08: PLL用作系统时钟*//*-------------打开相应外设时钟----------------------使能APB2外设的GPIOx 的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE);在我们给GPIO端口使能之前就已经默认设置好时钟为72MHZ -----------------------------------------------------*/}else{/*如果HSE启动失败,用户可以在这里添加处理错误代码*/}
}void MCO_GPIO_Config()
{GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);}

main.c

#include "stm32f10x.h"
#include "BSP_RCC_clkconfig.h"
#include "sys.h"void Delay(uint32_t t)
{while(t--);
}int main()
{//配置时钟频率HSE_SetSysClk(RCC_PLLMul_2); //RCC_PLLMul_x  (x = 1~16)MCO_GPIO_Config();             // PA8  外接示波器RCC_MCOConfig(RCC_MCO_SYSCLK); //在示波器上显示波形图,显示频率while(1){ Pin_out(CLK_PB, GPIO_Pin_0, HIGH);Delay(0xFFFFF);Pin_out(CLK_PB, GPIO_Pin_0, LOW);Delay(0xFFFFF);}return 0;
}

中断

中断/事件线

EXTI有20个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0和EXTI15,还有4个用于特点的外设事件。它们如下:

EXTI0中断/事件线:输入由PA0~PI0等组成。
EXTI1中断/事件线:输入由PA1~PI1等组成。
EXTI2中断/事件线:输入由PA2~PI2等组成。
EXTI3中断/事件线:输入由PA3~PI3等组成。
EXTI4中断/事件线:输入由PA4~PI4等组成。
EXTI5中断/事件线:输入由PA5~PI5等组成。
EXTI6中断/事件线:输入由PA6~PI6等组成。
EXTI7中断/事件线:输入由PA7~PI7等组成。
EXTI8中断/事件线:输入由PA8~PI8等组成。
EXTI9中断/事件线:输入由PA9~PI9等组成。
EXTI10中断/事件线:输入由PA10~PI10等组成。
EXTI11中断/事件线:输入由PA11~PI11等组成。
EXTI12中断/事件线:输入由PA12~PI12等组成。
EXTI13中断/事件线:输入由PA13~PI13等组成。
EXTI14中断/事件线:输入由PA14~PI15等组成。
EXTI15中断/事件线:输入由PA15~PI15等组成。
EXTI16中断/事件线:PVD输出。
EXTI17中断/事件线:RTC闹钟事件。
EXTI18中断/事件线:USB唤醒事件。
EXTI19中断/事件线:以太网唤醒事件(只适用互联型)。

// BSP_Exti.h

#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H#include "stm32f10x.h"void EXTI_key_Config();
void Delay_ms(uint32_t t);
#endif

// BSP_Exti.c

/*************************************************************************************************************************中断编程配置一般有3个要点:
1)使能外设某个中断,这个具体由每个每个外设的相关中断使能位控制。(配置NVIC_Config()函数)
2)初始化 NVIC_InitTypDef结构体,配置中断优先优先极分组,设置抢占优先级和子优先级,使能中断请求。(配置EXTI_Config()函数)
3)编写中断服务函数。(编写中断服务函数)1、初始化要连接到EXTI的GPIO
2、初始化EXTI用于产生中断/事件
3、初始化NVIC,用于处理中断
4、编写中断服务函数
5、main()函数**************************************************************************************************************************/#include "BSP_Exti.h"void Delay_ms(uint32_t t)
{uint32_t i = 0;SysTick_Config(72000); //改为72就是 us 级的定时for(i = 0; i < t; i++){while( !((SysTick->CTRL)  & (1 << 16)));}SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk); //关闭定时器
}void EXTI_NVIC_Config()
{NVIC_InitTypeDef NVIC_InitStruct;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);   //抢占优先级可选 0~1,响应优先级可选 0~7/**                                                               主         子*第0组:所有4位用于指定响应优先级                                 0            0~15*第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级   0~1       0~7*第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级    0~3       0~3*第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级    0~7       0~1*第4组:所有4位用于指定抢占式优先级                                 0~15      0**/NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; /*引脚0~4的对应输入 EXTIx_IRQn(x:0~4)    引脚5~9输入EXTI9_5_IRQn    引脚10~15输入EXTI15_10_IRQn */  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;  //抢占优先级为 1NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;          //响应优先级为 1NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;             //使能EXTI0中断NVIC_Init(&NVIC_InitStruct);    //初始化以上参数
}void EXTI_key_Config()
{   GPIO_InitTypeDef GPIO_InitStruct;EXTI_InitTypeDef EXTI_InitStruct;//配置中断优先级EXTI_NVIC_Config(); //1、初始化要连接到 EXTI 的GPIORCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入,就是GPIO默认电平由外部决定GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_Init(GPIOA, &GPIO_InitStruct);//2、初始化EXTI用于产生中断/事件RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  //打开时钟, 为了就是把GPIO作为中断输入源, 中断必须打开AFIO时钟GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择信号源 PA0 EXTI_InitStruct.EXTI_Line = EXTI_Line0;                 // 中断线: Pin_0 引脚EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;    //EXTI0 为中断模式     选用产生中断或者产生事件EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;  //上升沿中断,即PA0端口为高电平触发中断EXTI_InitStruct.EXTI_LineCmd = ENABLE;               //使能中断EXTI_Init(&EXTI_InitStruct);/*EXTI_Line:EXTI中断/事件线选择,可选 EXTI0 至 EXTI19,可参考表 18-1 选择。EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降 沿 触 发 ( EXTI_Trigger_Falling) 或 者 上 升 沿 和 下 降 沿 都 触 发( EXTI_Trigger_Rising_Falling)。EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用(DISABLE)。*//********中断服务函数命名类型*********************引脚0 ~ 4void EXTI0_IRQHandler()   void EXTI1_IRQHandler()      void EXTI2_IRQHandler()      void EXTI3_IRQHandler()    void EXTI4_IRQHandler()   //引脚5 ~ 9void EXTI9_5_IRQHandler() //引脚10 ~ 15void EXTI15_10_IRQHandler()       ************************************************/}

1)在 stm32f10x_it.c 中编写中断服务函数

中断服务函数命名类型:
引脚0 ~ 4
void EXTI0_IRQHandler()
void EXTI1_IRQHandler()
void EXTI2_IRQHandler()
void EXTI3_IRQHandler()
void EXTI4_IRQHandler()
//引脚5 ~ 9
void EXTI9_5_IRQHandler()
//引脚10 ~ 15
void EXTI15_10_IRQHandler()

void EXTI0_IRQHandler()      //中断服务函数有名字要求 “ void PPP_IRQHandler(void) ”
{if(EXTI_GetITStatus(EXTI_Line0) != RESET){GPIOB->ODR ^= GPIO_Pin_0;Delay_ms(300);}GPIO_SetBits(GPIOB, GPIO_Pin_0);EXTI_ClearITPendingBit(EXTI_Line0);  //清除中断
}

系统滴答定时器

// BSP_Systick.h

#ifndef __BSP_SYSTICK_H
#define __BSP_SYSTICK_H#include "stm32f10x.h"
#include "core_cm3.h"void Systick_Delay_us(uint32_t us);
void Systick_Delay_ms(uint32_t ms);#endif

// BSP_Systick.c

#include "BSP_Systick.h"void Systick_Delay_us(uint32_t us)
{uint32_t i = 0;//t = reload * (1 / clk)     reload = sysclk = 72MHZSysTick_Config(72); //Clk = 72M时,t = (72)*(1/72M) = 1us   即reload = 72  for(i = 0; i < us; i++){while( !((SysTick->CTRL) & (1 << 16)) );}SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk);
}void Systick_Delay_ms(uint32_t ms)    //如果编译错误就添加 "core_cm3.h" 头文件
{uint32_t i = 0;//t = reload * (1 / clk)     reload = sysclk = 72MHZ       t 不能大于 2^24 = 16777216SysTick_Config(72000);    //Clk = 72000M时,t = (72000)*(1/72M) = 1ms   即reload = 72000    就接受72000个脉冲用时1msfor(i = 0; i < ms; i++){while( !((SysTick->CTRL) & (1 << 16)) );}SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk);  //关闭定时器,一定要记得关闭
}//=*********************SysTick_Config()函数的原型****************************//                             一下内容不编译               #if 0   static __INLINE uint32_t SysTick_Config(uint32_t ticks){ if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);           /* 因为ticks这个参数是脉冲计数值,要被保存到重载寄存器 STK_LOAD 寄存器中,再由硬件把STK_LOAD值加载到当前计数值寄存器STK_VAL使用的,因为STK_LOAD和STK_VAL都是24位,所以在输入ticks大于可存储的最大值时,由这行代码检查出错误返回。*/SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* 当ticks的值没有错误后,就把ticks-1赋值到STK_LOAD寄存器中(注意是减1),当STK_VAL从ticks-1向下计数到0,则就经过了ticks个脉冲。(这里面用到的SysTick_LOAD_RELOAD_Msk宏(在keil环境下跟踪查看定义),这个宏是用来指示寄存器的“特定位置”或者是用来“位屏蔽”,所以叫位指示宏或位屏蔽宏,这个在操作寄存器代码中经常用到,通常都是用作控制寄存器某些位(“或”和“与”操作))*/NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);// 设置SysTick 中断优先级,默认为最低的优先级(1 << 4) - 1 = 16 - 1 = 15/* 调用了NVIC_SetPriority()函数配置了SysTick中断,所以我们就不需要再去手动配置。配置完后把STK_VAL寄存器重新赋值为0。*/  SysTick->VAL    = 0;                                        SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |  //使能定时器,就是打开定时器                SysTick_CTRL_TICKINT_Msk   | SysTick_CTRL_ENABLE_Msk;               /*向STK_CTRL写入SysTick timer的控制参数,配置为时钟,使能为计数至0引起中断,使能SysTick,接着SysTick就开始运行,做脉冲计数。(如果想使用AHB/8作为时钟,可以调用库函数SysTick_CLKSourceConfig()或者修改SysTick->CTRL的使能配置)*/return (0);                                                 }//******************************************************************************
#endif

串口通讯 USART1


勾选上 Use McroLIB 才可以用 printf() 函数

// bsp_usart.h

#ifndef __USART_H
#define __USART_H#include "stm32f10x.h"
#include <stdio.h>/*** 串口宏定义,不同的串口挂载的总线不一样,移植时需要修改这几个宏*/
#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_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandlervoid USART_Config(void);
void Usart_SendByte( USART_TypeDef *pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef *pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef *pUSARTx, uint16_t ch);#endif /* __USART_H */

// bsp_usart.c

#include "bsp_usart.h"/*** @brief  USART GPIO 配置,工作参数配置* @param  无* @retval 无*/
void USART_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 打开串口GPIO的时钟DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);  // PA时钟// 打开串口外设的时钟DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);  // RCC_APB2Periph_USART1  打开串口1的时钟// 将USART Tx的GPIO配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;   // Pin_9引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // GPIOA 使能// 将USART Rx的GPIO配置为浮空输入模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;      // Pin_10引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;       // 浮空输入GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);   // GPIOA 使能// 配置串口的工作参数// 配置波特率USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;   //115200 波特率// 指定帧中发送或接收的数据比特数USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //8位或者9位// 配置停止位USART_InitStructure.USART_StopBits = USART_StopBits_1;      //停止位 :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);    //USART1 完成串口1的初始化// 使能串口USART_Cmd(DEBUG_USARTx, ENABLE);                   // 启用USART1 的外设
}/*****************  发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef *pUSARTx, uint8_t ch)
{/* 发送一个字节数据到USART */USART_SendData(pUSARTx, ch);/* 等待发送数据寄存器为空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef *pUSARTx, char *str)
{unsigned int k = 0;do{Usart_SendByte( pUSARTx, *(str + k) );k++;}while(*(str + k) != '\0');/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET){}
}/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef *pUSARTx, uint16_t ch)
{uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch & 0XFF00) >> 8;/* 取出低八位 */temp_l = ch & 0XFF;/* 发送高八位 */USART_SendData(pUSARTx, temp_h);while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 发送低八位 */USART_SendData(pUSARTx, temp_l);while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口 */USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);return (ch);
}///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USARTx);
}

// main 函数

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h" static void Show_Message(void);int main(void)
{   char ch;/* 初始化RGB彩灯 */LED_GPIO_Config();/* 初始化USART 配置模式为 115200 8-N-1 */USART_Config();/* 打印指令输入提示信息 */Show_Message();while(1){    /* 获取字符指令 */ch=getchar();printf("接收到字符:%c\n",ch);/* 根据字符指令控制RGB彩灯颜色 */switch(ch){case '1':LED_RED;break;case '2':LED_GREEN;break;case '3':LED_BLUE;break;case '4':LED_YELLOW;break;case '5':LED_PURPLE;break;case '6':LED_CYAN;break;case '7':LED_WHITE;break;case '8':LED_RGBOFF;break;default:/* 如果不是指定指令字符,打印提示信息 */Show_Message();break;      }   }
}/*** @brief  打印指令输入提示信息* @param  无* @retval 无*/
static void Show_Message(void)
{printf("\r\n   这是一个通过串口通信指令控制RGB彩灯实验 \n");printf("使用  USART  参数为:%d 8-N-1 \n",DEBUG_USART_BAUDRATE);printf("开发板接到指令后控制RGB彩灯颜色,指令对应如下:\n");printf("   指令   ------ 彩灯颜色 \n");printf("     1    ------    红 \n");printf("     2    ------    绿 \n");printf("     3    ------    蓝 \n");printf("     4    ------    黄 \n");printf("     5    ------    紫 \n");printf("     6    ------    青 \n");printf("     7    ------    白 \n");printf("     8    ------    灭 \n");
}/*********************************************END OF FILE**********************/

DMA (直接存储器访问)

DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。

在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

请求
CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
响应
DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
传输
DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
结束
当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。
由此可见,DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,使CPU的效率大为提高。

// bsp_usart_dma.h

#ifndef __USARTDMA_H
#define __USARTDMA_H#include "stm32f10x.h"
#include <stdio.h>// 串口工作参数宏定义
#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_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10// 串口对应的DMA请求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel4
// 外设寄存器地址
#define  USART_DR_ADDRESS        (USART1_BASE+0x04)
// 一次发送的数据量
#define  SENDBUFF_SIZE            5000void USART_Config(void);
void USARTx_DMA_Config(void);#endif /* __USARTDMA_H */

// bsp_usart_dma.c

#include "bsp_usart_dma.h"uint8_t SendBuff[SENDBUFF_SIZE];/*** @brief  USART GPIO 配置,工作参数配置* @param  无* @retval 无*/
void USART_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 打开串口GPIO的时钟DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);// 打开串口外设的时钟DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);// 将USART Tx的GPIO配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;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 = GPIO_Mode_IN_FLOATING;GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);// 配置串口的工作参数// 配置波特率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);// 使能串口USART_Cmd(DEBUG_USARTx, ENABLE);
}/*****************  发送一个字节 **********************/
void Usart_SendByte( USART_TypeDef *pUSARTx, uint8_t ch)
{/* 发送一个字节数据到USART */USART_SendData(pUSARTx, ch);/* 等待发送数据寄存器为空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}/****************** 发送8位的数组 ************************/
void Usart_SendArray( USART_TypeDef *pUSARTx, uint8_t *array, uint16_t num)
{uint8_t i;for(i = 0; i < num; i++){/* 发送一个字节数据到USART */Usart_SendByte(pUSARTx, array[i]);}/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef *pUSARTx, char *str)
{unsigned int k = 0;do{Usart_SendByte( pUSARTx, *(str + k) );k++;}while(*(str + k) != '\0');/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET){}
}/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef *pUSARTx, uint16_t ch)
{uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch & 0XFF00) >> 8;/* 取出低八位 */temp_l = ch & 0XFF;/* 发送高八位 */USART_SendData(pUSARTx, temp_h);while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 发送低八位 */USART_SendData(pUSARTx, temp_l);while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口 */USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);return (ch);
}///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USARTx);
}/*** @brief  USARTx TX DMA 配置,内存到外设(USART1->DR)* @param  无* @retval 无*/
void USARTx_DMA_Config(void)
{DMA_InitTypeDef DMA_InitStructure;   //定义变量结构体// 开启DMA时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 设置DMA源地址:串口数据寄存器地址*/DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;   //USART_DR 数据寄存器的地址 即外设地址// 内存地址(要传输的变量的指针)DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;    //数组指针,就是存储器的地址// 方向:从内存到外设DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //仅有外设到存储器和存储器到外设// 传输大小DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;  //缓冲区的大小 即 5000// 外设地址不增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 内存地址自增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 外设数据单位DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //单位为 :字节// 内存数据单位                                                        //外设数据单位和内存数据单位必须一致DMA_InitStructure.DMA_MemoryDataSize = 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(USART_TX_DMA_CHANNEL, &DMA_InitStructure);  //完成初始化// 使能DMADMA_Cmd (USART_TX_DMA_CHANNEL, ENABLE);               //进行使能
}

// main.c


// DMA 存储器到外设(串口)数据传输实验#include "stm32f10x.h"
#include "bsp_usart_dma.h"
#include "bsp_led.h"extern uint8_t SendBuff[SENDBUFF_SIZE];
static void Delay(__IO u32 nCount); /*** @brief  主函数* @param  无* @retval 无*/
int main(void)
{uint16_t i;/* 初始化USART */USART_Config(); /* 配置使用DMA模式 */USARTx_DMA_Config();/* 配置RGB彩色灯 */LED_GPIO_Config();//printf("\r\n USART1 DMA TX 测试 \r\n");/*填充将要发送的数据*/for(i=0;i<SENDBUFF_SIZE;i++){SendBuff[i]    = 'L';      //把字节 'P' 赋值数组,即放在缓存空间内}/*为演示DMA持续运行而CPU还能处理其它事情,持续使用DMA发送数据,量非常大,*长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,*或把DMA配置中的循环模式改为单次模式*/        /* USART1 向 DMA发出TX请求 */USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); //发起请求之后自动在串口上显示数据/* 此时CPU是空闲的,可以干其他的事情 */  //例如同时控制LEDwhile(1){LED1_TOGGLEDelay(0xFFFFF);}
}static void Delay(__IO uint32_t nCount)     //简单的延时函数
{for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/

IIC协议

例题:I2C—读写EEPROM

// bsp_i2c_ee.h

#ifndef __I2C_EE_H
#define __I2C_EE_H#include "stm32f10x.h"/**************************I2C参数定义,I2C1或I2C2********************************/
#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  //*/* 这个地址只要与STM32外挂的I2C器件地址不一样即可 */
#define I2Cx_OWN_ADDRESS7      0X0A   /* AT24C01/02每页有8个字节 */
#define I2C_PageSize           8/* AT24C04/08A/16A每页有16个字节 */
//#define I2C_PageSize           16 /*等待超时时间*/
#define I2CT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT         ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))/*信息输出*/
#define EEPROM_DEBUG_ON         0#define EEPROM_INFO(fmt,arg...)           printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...)          printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...)          do{\if(EEPROM_DEBUG_ON)\printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\}while(0)/* * 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_Block0_ADDRESS 0xA0   /* E2 = 0 */
//#define EEPROM_Block1_ADDRESS 0xA2 /* E2 = 0 */
//#define EEPROM_Block2_ADDRESS 0xA4 /* E2 = 0 */
//#define EEPROM_Block3_ADDRESS 0xA6 /* E2 = 0 */void I2C_EE_Init(void);
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite);
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr);
uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite);
uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead);
void I2C_EE_WaitEepromStandbyState(void);#endif /* __I2C_EE_H */

// bsp_i2c_ee.c

#include "./i2c/bsp_i2c_ee.h"
#include "./usart/bsp_usart.h"uint16_t EEPROM_ADDRESS;static __IO uint32_t  I2CTimeout = I2CT_LONG_TIMEOUT;static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);/*** @brief  I2C I/O配置* @param  无* @retval 无*/
static void I2C_GPIO_Config(void)
{GPIO_InitTypeDef  GPIO_InitStructure;/* 使能与 I2C 有关的时钟 */EEPROM_I2C_APBxClock_FUN ( EEPROM_I2C_CLK, ENABLE );  //I2c1 挂载在APB1上EEPROM_I2C_GPIO_APBxClock_FUN ( EEPROM_I2C_GPIO_CLK, ENABLE ); //GPIOB时钟/* I2C_SCL、I2C_SDA*/GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;       //PB6GPIO_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;       //PB7GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;         // 开漏输出GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);}/*** @brief  I2C 工作模式配置* @param  无* @retval 无*/
static void I2C_Mode_Configu(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);
}/*** @brief  I2C 外设(EEPROM)初始化* @param  无* @retval 无*/
void I2C_EE_Init(void)
{//配置PB6, PB7端口,实际就是配置SCL、SDA I2C两条总线I2C_GPIO_Config();//配置I2C的工作模式I2C_Mode_Configu();/* 根据头文件i2c_ee.h中的定义来选择EEPROM的设备地址 */
#ifdef EEPROM_Block0_ADDRESS/* 选择 EEPROM Block0 来写入 */EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
#endif#ifdef EEPROM_Block1_ADDRESS/* 选择 EEPROM Block1 来写入 */EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif#ifdef EEPROM_Block2_ADDRESS/* 选择 EEPROM Block2 来写入 */EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif#ifdef EEPROM_Block3_ADDRESS/* 选择 EEPROM Block3 来写入 */EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}/*** @brief   将缓冲区中的数据写到I2C EEPROM中* @param*     @arg pBuffer:缓冲区指针*        @arg WriteAddr:写地址*     @arg NumByteToWrite:写的字节数* @retval  无*/
void I2C_EE_BufferWrite(u8 *pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;Addr = WriteAddr % I2C_PageSize;count = I2C_PageSize - Addr;NumOfPage =  NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;/* If WriteAddr is I2C_PageSize aligned  */if(Addr == 0){/* If NumByteToWrite < I2C_PageSize */if(NumOfPage == 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* If 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();}}}/* If WriteAddr is not I2C_PageSize aligned  */else{/* If NumByteToWrite < I2C_PageSize */if(NumOfPage == 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* If NumByteToWrite > I2C_PageSize */else{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;if(count != 0){I2C_EE_PageWrite(pBuffer, WriteAddr, count);I2C_EE_WaitEepromStandbyState();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();}}}
}/*** @brief   写一个字节到I2C EEPROM中* @param*     @arg pBuffer:缓冲区指针*        @arg WriteAddr:写地址* @retval  无*/
uint32_t I2C_EE_ByteWrite(u8 *pBuffer, u8 WriteAddr)
{/* Send STRAT condition */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV5 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);}I2CTimeout = I2CT_FLAG_TIMEOUT;/* Send EEPROM address for write */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);/* Test on EV6 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);}/* Send the EEPROM's internal address to write to */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV8 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);}/* Send the byte to be written */I2C_SendData(EEPROM_I2Cx, *pBuffer);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV8 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);}/* Send STOP condition */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);return 1;
}/*** @brief   在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数*          不能超过EEPROM页的大小,AT24C02每页有8个字节* @param*     @arg pBuffer:缓冲区指针*        @arg WriteAddr:写地址*     @arg NumByteToWrite:写的字节数* @retval  无*/
uint32_t I2C_EE_PageWrite(u8 *pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{I2CTimeout = I2CT_LONG_TIMEOUT;while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);}/* Send START condition */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV5 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);}/* Send EEPROM address for write */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV6 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);}/* Send the EEPROM's internal address to write to */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV8 and clear it */while(! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);}/* While there is data to be written */while(NumByteToWrite--){/* Send the current byte */I2C_SendData(EEPROM_I2Cx, *pBuffer);/* Point to the next byte to be written */pBuffer++;I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV8 and clear it */while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);}}/* Send STOP condition */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);return 1;
}/*** @brief   从EEPROM里面读取一块数据* @param*       @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针*     @arg WriteAddr:接收数据的EEPROM的地址*     @arg NumByteToWrite:要从EEPROM读取的字节数* @retval  无*/
uint32_t I2C_EE_BufferRead(u8 *pBuffer, u8 ReadAddr, u16 NumByteToRead)
{I2CTimeout = I2CT_LONG_TIMEOUT;//*((u8 *)0x4001080c) |=0x80;while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);}/* Send START condition */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);//*((u8 *)0x4001080c) &=~0x80;I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV5 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);}/* Send EEPROM address for write */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV6 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);}/* Clear EV6 by setting again the PE bit */I2C_Cmd(EEPROM_I2Cx, ENABLE);/* Send the EEPROM's internal address to write to */I2C_SendData(EEPROM_I2Cx, ReadAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV8 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);}/* Send STRAT condition a second time */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV5 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);}/* Send EEPROM address for read */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);I2CTimeout = I2CT_FLAG_TIMEOUT;/* Test on EV6 and clear it */while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);}/* While there is data to be read */while(NumByteToRead){if(NumByteToRead == 1){/* Disable Acknowledgement */I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);/* Send STOP Condition */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);}/* Test on EV7 and clear it */I2CTimeout = I2CT_LONG_TIMEOUT;while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) == 0){if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);}{/* Read a byte from the EEPROM */*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);/* Point to the next location where the byte read will be saved */pBuffer++;/* Decrement the read bytes counter */NumByteToRead--;}}/* Enable Acknowledgement to be ready for another reception */I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);return 1;
}/*** @brief  Wait for EEPROM Standby state* @param  无* @retval 无*/
void I2C_EE_WaitEepromStandbyState(void)
{vu16 SR1_Tmp = 0;do{/* Send START condition */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);/* Read I2C1 SR1 register */SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);/* Send EEPROM address for write */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);}while(!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));/* Clear AF flag */I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);/* STOP condition */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}/*** @brief  Basic management of the timeout situation.* @param  errorCode:错误代码,可以用来定位是哪个环节出错.* @retval 返回0,表示IIC读取失败.*/
static  uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{/* Block communication and all processes */EEPROM_ERROR("I2C 等待超时!errorCode = %d", errorCode);return 0;
}
/*********************************************END OF FILE**********************/

// mian.c

#include "stm32f10x.h"
#include "./led/bsp_led.h"
#include "./usart/bsp_usart.h"
#include "./i2c/bsp_i2c_ee.h"
#include <string.h>#define  EEP_Firstpage      0x00
uint8_t I2c_Buf_Write[256];
uint8_t I2c_Buf_Read[256];
uint8_t I2C_Test(void);/*** @brief  主函数* @param  无* @retval 无*/
int main(void)
{LED_GPIO_Config();LED_BLUE;/* 串口初始化 */USART_Config();printf("\r\n 这是一个I2C外设(AT24C02)读写测试例程 \r\n");/* I2C 外设初(AT24C02)始化 */I2C_EE_Init();//EEPROM 读写测试if(I2C_Test() == 1){LED_GREEN;}else{LED_RED;}while (1){}
}/*** @brief  I2C(AT24C02)读写测试* @param  无* @retval 正常返回1,异常返回0*/
uint8_t I2C_Test(void)
{uint16_t i;printf("写入的数据\n\r");for ( i = 0; i <= 255; i++ ) //填充缓冲{I2c_Buf_Write[i] = i;printf("0x%02X ", I2c_Buf_Write[i]);if(i % 16 == 15)printf("\n\r");}//将I2c_Buf_Write中顺序递增的数据写入EERPOM中I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256);EEPROM_INFO("\n\r写成功\n\r");EEPROM_INFO("\n\r读出的数据\n\r");//将EEPROM读出数据顺序保持到I2c_Buf_Read中I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);//将I2c_Buf_Read中的数据通过串口打印for (i = 0; i < 256; i++){if(I2c_Buf_Read[i] != I2c_Buf_Write[i]){EEPROM_ERROR("0x%02X ", I2c_Buf_Read[i]);EEPROM_ERROR("错误:I2C EEPROM写入与读出的数据不一致\n\r");return 0;}printf("0x%02X ", I2c_Buf_Read[i]);if(i % 16 == 15)printf("\n\r");}EEPROM_INFO("I2C(AT24C02)读写测试成功\n\r");return 1;
}
/*********************************************END OF FILE**********************/

SPI协议

什么是SPI
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI主从模式
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

SPI信号线
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

// bsp_spi_flash.h

#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H#include "stm32f10x.h"
#include <stdio.h>//#define  sFLASH_ID              0xEF3015   //W25X16
//#define  sFLASH_ID              0xEF4015   //W25Q16
//#define  sFLASH_ID              0XEF4018   //W25Q128
#define  sFLASH_ID              0XEF4017    //W25Q64#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256/*命令定义-开头*******************************/
#define W25X_WriteEnable                0x06
#define W25X_WriteDisable               0x04
#define W25X_ReadStatusReg              0x05
#define W25X_WriteStatusReg             0x01
#define W25X_ReadData                   0x03
#define W25X_FastReadData               0x0B
#define W25X_FastReadDual               0x3B
#define W25X_PageProgram                0x02
#define W25X_BlockErase                 0xD8
#define W25X_SectorErase                0x20
#define W25X_ChipErase                  0xC7
#define W25X_PowerDown                  0xB9
#define W25X_ReleasePowerDown           0xAB
#define W25X_DeviceID                   0xAB
#define W25X_ManufactDeviceID           0x90
#define W25X_JedecDeviceID              0x9F/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag                  0x01
#define Dummy_Byte                0xFF
/*命令定义-结尾*******************************//*SPI接口定义-开头****************************/
#define      FLASH_SPIx                        SPI1
#define      FLASH_SPI_APBxClock_FUN           RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CLK                     RCC_APB2Periph_SPI1//CS(NSS)引脚 片选选普通GPIO即可
#define      FLASH_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define      FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOA
#define      FLASH_SPI_CS_PORT                 GPIOA
#define      FLASH_SPI_CS_PIN                  GPIO_Pin_4//SCK引脚
#define      FLASH_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define      FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA
#define      FLASH_SPI_SCK_PORT                GPIOA
#define      FLASH_SPI_SCK_PIN                 GPIO_Pin_5
//MISO引脚
#define      FLASH_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA
#define      FLASH_SPI_MISO_PORT               GPIOA
#define      FLASH_SPI_MISO_PIN                GPIO_Pin_6
//MOSI引脚
#define      FLASH_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA
#define      FLASH_SPI_MOSI_PORT               GPIOA
#define      FLASH_SPI_MOSI_PIN                GPIO_Pin_7#define     SPI_FLASH_CS_LOW()                GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define      SPI_FLASH_CS_HIGH()               GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )/*SPI接口定义-结尾****************************//*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))/*信息输出*/
#define FLASH_DEBUG_ON         1#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\if(FLASH_DEBUG_ON)\printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\}while(0)void SPI_FLASH_Init(void);
void SPI_FLASH_SectorErase(u32 SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferWrite(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferRead(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead);
u32 SPI_FLASH_ReadID(void);
u32 SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(u32 ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);u8 SPI_FLASH_ReadByte(void);
u8 SPI_FLASH_SendByte(u8 byte);
u16 SPI_FLASH_SendHalfWord(u16 HalfWord);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);#endif /* __SPI_FLASH_H */

// bsp_spi_flash.c

#include "./flash/bsp_spi_flash.h"static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);/*** @brief  SPI_FLASH初始化* @param  无* @retval 无*/
void SPI_FLASH_Init(void)
{/**  SPI1:  * *  NSS  --> PA4*    CLK  --> PA5*  MISO --> PA6 *     MOSI --> PA7
***/    SPI_InitTypeDef  SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;/* 使能SPI时钟 */FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );  //SPI1 挂载在APB2上/* 使能SPI引脚相关的时钟      打开CS(NSS)、SCK、MISO、MOSI四个引脚的时钟     都在APB2上    */FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK | FLASH_SPI_SCK_CLK |FLASH_SPI_MISO_PIN | FLASH_SPI_MOSI_PIN, ENABLE );/* 配置SPI的 CS(NSS)引脚,普通IO即可 */GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;       // PA4GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   //推挽输出GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);  /* 配置SPI的 SCK引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;    //PA5GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;       //复用推挽输出GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI的 MISO引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN; //PA6GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI的 MOSI引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;   //PA7GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);/* 停止信号 FLASH: CS引脚高电平*/SPI_FLASH_CS_HIGH();  //CS(NSS) --> 高电平   通讯停止信号/* SPI 模式配置 */// FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHASPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    //指定SPI单向或双向数据模式, 全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                         //SPI工作模式, 主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   //指定SPI数据大小, 8 bitSPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                          // CPOL --> 1  SCK空闲为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                        // CPHA --> 0  偶数边沿采样SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //指定NSS信号是否由硬件(NSS引脚)或通过软件使用SSI位,选择软件SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;   //指定波特率预分配器的值用于配置发送和接收SCK时钟, 即4分频, 72/4 = 18MHZSPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                    //指定数据传输是从MSB位还是从LSB位开始。 默认MSB先行SPI_InitStructure.SPI_CRCPolynomial = 7;                           //指定用于CRC计算的多项式。SPI_Init(FLASH_SPIx , &SPI_InitStructure);                          //参数写入寄存器/* 使能 SPI  */SPI_Cmd(FLASH_SPIx , ENABLE);                                         //开启SPI外设}
/*** @brief  擦除FLASH扇区* @param  SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  擦除FLASH扇区,整片擦除* @param  无* @retval 无*/
void SPI_FLASH_BulkErase(void)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 整块 Erase *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送整块擦除指令*/SPI_FLASH_SendByte(W25X_ChipErase);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param   pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* @retval 无*/
void SPI_FLASH_PageWrite(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 写页写指令*/SPI_FLASH_SendByte(W25X_PageProgram);/*发送写地址的高位*/SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*发送写地址的中位*/SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*发送写地址的低位*/SPI_FLASH_SendByte(WriteAddr & 0xFF);if(NumByteToWrite > SPI_FLASH_PerWritePageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;FLASH_ERROR("SPI_FLASH_PageWrite too large!");}/* 写入数据*/while (NumByteToWrite--){/* 发送当前要写入的字节数据 */SPI_FLASH_SendByte(*pBuffer);/* 指向下一字节数据 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待写入完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区* @param pBuffer,要写入数据的指针* @param  WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度* @retval 无*/
void SPI_FLASH_BufferWrite(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/Addr = WriteAddr % SPI_FLASH_PageSize;/*差count个数据值,刚好可以对齐到页地址*/count = SPI_FLASH_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;/*mod运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */if (Addr == 0){/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*先把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}/* 若地址与 SPI_FLASH_PageSize 不对齐  */else{/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/if (NumOfSingle > count){temp = NumOfSingle - count;/*先写满当前页*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;/*再写剩余的数据*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else /*当前页剩余的count个位置能写完NumOfSingle个数据*/{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*地址不对齐多出的count分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* 先写完count个数据,为的是让下一次要写的地址对齐 */SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);/* 接下来就重复地址对齐的情况 */WriteAddr +=  count;pBuffer += count;/*把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}
}/*** @brief  读取FLASH数据* @param   pBuffer,存储读出数据的指针* @param   ReadAddr,读取地址* @param   NumByteToRead,读取数据长度* @retval 无*/
void SPI_FLASH_BufferRead(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead)
{/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送 读 指令 */SPI_FLASH_SendByte(W25X_ReadData);/* 发送 读 地址高位 */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);/* 发送 读 地址低位 */SPI_FLASH_SendByte(ReadAddr & 0xFF);/* 读取数据 */while (NumByteToRead--) /* while there is data to be read */{/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();
}/*** @brief  读取FLASH ID* @param  无* @retval FLASH ID*/
u32 SPI_FLASH_ReadID(void)
{u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;/* 开始通讯:CS低电平 */SPI_FLASH_CS_LOW();/* 发送JEDEC指令,读取ID */SPI_FLASH_SendByte(W25X_JedecDeviceID);/* 读取一个字节数据 */Temp0 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp1 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp2 = SPI_FLASH_SendByte(Dummy_Byte);/* 停止通讯:CS高电平 */SPI_FLASH_CS_HIGH();/*把数据组合起来,作为函数的返回值*/Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}
/*** @brief  读取FLASH Device ID* @param    无* @retval FLASH Device ID*/
u32 SPI_FLASH_ReadDeviceID(void)
{u32 Temp = 0;/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "RDID " instruction */SPI_FLASH_SendByte(W25X_DeviceID);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);/* Read a byte from the FLASH */Temp = SPI_FLASH_SendByte(Dummy_Byte);/* Deselect the FLASH: Chip Select high */SPI_FLASH_CS_HIGH();return Temp;
}
/*******************************************************************************
* Function Name  : SPI_FLASH_StartReadSequence
* Description    : Initiates a read data byte (READ) sequence from the Flash.
*                  This is done by driving the /CS line low to select the device,
*                  then the READ instruction is transmitted followed by 3 bytes
*                  address. This function exit and keep the /CS line low, so the
*                  Flash still being selected. With this technique the whole
*                  content of the Flash is read with a single READ instruction.
* Input          : - ReadAddr : FLASH's internal address to read from.
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_FLASH_StartReadSequence(u32 ReadAddr)
{/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "Read from Memory " instruction */SPI_FLASH_SendByte(W25X_ReadData);/* Send the 24-bit address of the address to read from -----------------------*//* Send ReadAddr high nibble address byte */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* Send ReadAddr medium nibble address byte */SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);/* Send ReadAddr low nibble address byte */SPI_FLASH_SendByte(ReadAddr & 0xFF);
}/*** @brief  使用SPI读取一个字节的数据* @param  无* @retval 返回接收到的数据*/
u8 SPI_FLASH_ReadByte(void)
{return (SPI_FLASH_SendByte(Dummy_Byte));
}/*** @brief  使用SPI发送一个字节的数据* @param  byte:要发送的数据* @retval 返回接收到的数据*/
u8 SPI_FLASH_SendByte(u8 byte)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , byte);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @brief  使用SPI发送两个字节的数据* @param  byte:要发送的数据* @retval 返回接收到的数据*/
u16 SPI_FLASH_SendHalfWord(u16 HalfWord)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , HalfWord);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @brief  向FLASH发送 写使能 命令* @param  none* @retval none*/
void SPI_FLASH_WriteEnable(void)
{/* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送写使能命令*/SPI_FLASH_SendByte(W25X_WriteEnable);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();
}/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag                  0x01/*** @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕* @param  none* @retval none*/
void SPI_FLASH_WaitForWriteEnd(void)
{u8 FLASH_Status = 0;/* 选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 读状态寄存器 命令 */SPI_FLASH_SendByte(W25X_ReadStatusReg);/* 若FLASH忙碌,则等待 */do{/* 读取FLASH芯片的状态寄存器 */FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);}while ((FLASH_Status & WIP_Flag) == SET);  /* 正在写入标志 *//* 停止信号  FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}//进入掉电模式
void SPI_Flash_PowerDown(void)
{/* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送 掉电 命令 */SPI_FLASH_SendByte(W25X_PowerDown);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();
}//唤醒
void SPI_Flash_WAKEUP(void)
{/*选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 上电 命令 */SPI_FLASH_SendByte(W25X_ReleasePowerDown);/* 停止信号 FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}/*** @brief  等待超时回调函数* @param  None.* @retval None.*/
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{/* 等待超时后的处理,输出错误信息 */FLASH_ERROR("SPI 等待超时!errorCode = %d", errorCode);return 0;
}/*********************************************END OF FILE**********************/

// main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;/* 获取缓冲区的长度 */
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define  BufferSize (countof(Tx_Buffer)-1)#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = "感谢您选用野火stm32开发板\r\n";
uint8_t Rx_Buffer[BufferSize];__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;// 函数原型声明
void Delay(__IO uint32_t nCount);
TestStatus Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength);/** 函数名:main* 描述  :主函数* 输入  :无* 输出  :无*/
int main(void)
{LED_GPIO_Config();LED_BLUE;/* 配置串口为:115200 8-N-1 */USART_Config();printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");/* 8M串行flash W25Q64初始化 */SPI_FLASH_Init();/* 获取 Flash Device ID */DeviceID = SPI_FLASH_ReadDeviceID();Delay( 200 );/* 获取 SPI Flash ID */FlashID = SPI_FLASH_ReadID();printf("\r\n FlashID is 0x%X,\Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);/* 检验 SPI Flash ID */if (FlashID == sFLASH_ID){printf("\r\n 检测到串行flash W25Q64 !\r\n");/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */// 这里擦除4K,即一个扇区,擦除的最小单位是扇区SPI_FLASH_SectorErase(FLASH_SectorToErase);/* 将发送缓冲区的数据写到flash中 */// 这里写一页,一页的大小为256个字节SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);/* 将刚刚写入的数据读出来放到接收缓冲区中 */SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);/* 检查写入的数据与读出的数据是否相等 */TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);if( PASSED == TransferStatus1 ){LED_GREEN;printf("\r\n 8M串行flash(W25Q64)测试成功!\n\r");}else{LED_RED;printf("\r\n 8M串行flash(W25Q64)测试失败!\n\r");}}// if (FlashID == sFLASH_ID)else// if (FlashID == sFLASH_ID){LED_RED;printf("\r\n 获取不到 W25Q64 ID!\n\r");}while(1);
}/** 函数名:Buffercmp* 描述  :比较两个缓冲区中的数据是否相等* 输入  :-pBuffer1     src缓冲区指针*         -pBuffer2     dst缓冲区指针*         -BufferLength 缓冲区长度* 输出  :无* 返回  :-PASSED pBuffer1 等于   pBuffer2*         -FAILED pBuffer1 不同于 pBuffer2*/
TestStatus Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength)
{while(BufferLength--){if(*pBuffer1 != *pBuffer2){return FAILED;}pBuffer1++;pBuffer2++;}return PASSED;
}void Delay(__IO uint32_t nCount)
{for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/

基于野火霸道的 STM32F103 代码集合相关推荐

  1. DL之GRU:GRU算法相关论文、建立过程(基于TF)、相关思路配图集合、TF代码实现

    DL之GRU:GRU算法相关论文.建立过程(基于TF).相关思路配图集合.TF代码实现 目录 GRU算法相关论文 GRU算法建立过程(基于TF) GRU算法的TF代码实现 GRU算法相关论文 GRU是 ...

  2. 免费资源 | ActiveReports 报表控件发布多平台 Demo 代码集合

    近期,ActiveReports 产品开发组的小伙伴针对大家比较关注的报表功能.常见问题.经典实现,特意准备了一个Demo代码集合,涉及WinFormss \ ASP.NET \ MVC 多个技术平台 ...

  3. 基于DM642的X264开源代码实现的研究

    基于DM642的X264开源代码实现的研究 文章发表于:2008-09-08 11:24 作者:谭超 王库 傅颖 来源:微计算机信息 摘要:本文概述了H.264视频压缩编码标准的主要特性,简要介绍了当 ...

  4. 基于野火F407骄阳开发板的苹果采摘机器人机械臂的采摘轨迹与夹持器的采摘动作的设计(1)

    基于野火F407骄阳开发板的苹果采摘机器人机械臂的采摘轨迹与夹持器的采摘动作的设计(1) 苹果采摘机器人 1.采摘流程与硬件设计 2.机械臂驱动以及采摘轨迹设计 2.1.台达A2电机驱动实现 2.2. ...

  5. FPGA----ZCU106基于axi-hp通道的pl与ps数据交互(全网唯一最详)

    1.大家好,今天给大家带来的内容是,基于AXI4协议的采用AXI-HP通道完成PL侧数据发送至PS侧(PS侧数据发送至PL侧并没有实现,但是保留了PL读取PS测数据的接口) 2.如果大家用到SoC这种 ...

  6. 基于keil5自动配置stm32f103标准库的官网freertos移植

    基于keil5自动配置stm32f103标准库的官网freertos移植 前言 序言:利用keil5自动配置stm32f103标准库工程 闲话 正式篇 移植官网FreeRTOS 前言   当笔者学习s ...

  7. 连连看+php,java基于swing实现的连连看代码

    本文实例讲述了java基于swing实现连连看代码.分享给大家供大家参考. 主要功能代码如下:package llkan; import javax.swing.*; import java.awt. ...

  8. 隧道野蛮模式_基于虚拟隧道的IPsec -华三 MSR26 路由器对接Juniper SSG

    我会经常给大家分享各种IT技术文章,工程狮/程序猿们多多关注哟- 业务需要,做个IPSec,本来想做传统的基于策略的IPsec,后面想了下还是做基于虚拟隧道的IPSec*后续方便扩展.这种不同厂商设备 ...

  9. Freemarker基于模板文件静态化代码示例

    Freemarker基于模板文件静态化代码示例

  10. python直方图均衡化代码_基于matlab的直方图均衡化代码

    基于matlab的直方图均衡化代码 2007-04-15 20:15 clear all %一,图像的预处理,读入彩***像将其灰度化 PS=imread('1.jpg');             ...

最新文章

  1. python中module用法_Python学习之module用法
  2. 【Android 多媒体开发】 MediaPlayer 状态机 接口 方法 解析
  3. 八十三、Eureka实现相互注册
  4. 【基础】ORACLE中on commit preserve rows和 on commit delete rows的区别
  5. 【LeetCode笔记】剑指 Offer 57-. 和为s的两个数字 (Java、对撞双指针)
  6. 基于TableStore/MaxCompute的数据采集分析系统介绍
  7. 揭秘基于MBSE集成化的汽车电子解决方案
  8. java mac 怎么删_做java服务器开发,并发布到linux,那MacBookPro开发是绝佳工具
  9. struts2 tutor
  10. wordpress元素查找PHP文件,小部件中的php按类获取元素(wordpress)
  11. Tornado 一些资料
  12. PLC液压控制系列之比例流量阀结构分析
  13. 辨别尸体死亡时间! [转]
  14. 中国电信天翼空间发布移动应用开发运营平台
  15. element table相同数据行合并
  16. USB设备运行不正常,windows无法识别的问题
  17. sublime Text 3 javaScript代码自动提示插件安装步骤
  18. How to Generate PDF in Ruby on Rails(HowtoGeneratePDFs) ZT
  19. [Tableau] 饼图绘制
  20. Alpha865qqz勒索病毒解密

热门文章

  1. java cookie 跨域共享_跨域共享cookie
  2. 三星note10安装linux,三星Note10/Note10+新款Dex已支持Win10/macOS
  3. 新手入门:PyCharm 的使用
  4. CodeSniffer使用教程
  5. Python OpenCV 保存mp4视频
  6. 什么是CDN,网站被攻击时该怎么防
  7. Java轻量级缓存Ehcache与SpringBoot整合
  8. 睡觉计算机主机在身旁好么,笔记本电脑的睡眠和休眠之间有什么区别
  9. 【天光学术】体育论文:乒乓球少年运动员专项身体素质训练探究(节选)
  10. PDF中动态添加图片和文字