stm32入门最全笔记-洋桃电子对照目录

stm32命名规范

电路基础

tips:换算的时候要换成V、A、Ω

TTL和CMOS电平

单片机一般TTL电平

推挽驱动

灌电流驱动

有电压差就可以量,灌电流驱动更量,能达到20mA

上拉电阻

给定一个初始值,防止外部影响

数据手册

STM32F103X8-B数据手册(中文)-20150727-CD00161566_ZHV10.pdf

STM32F103X8-B数据手册(英文)-20160119-CD00161566_ENV17.pdf

内核

内核:ARM 32位的Cortex™-M3 CPU

− 最高72MHz工作频率,在存储器的0等待周期访问时可达1.25DMips/MHz(Dhrystone 2.1)

− 单周期乘法和硬件除法

存储器

− 从64K或128K字节的闪存程序存储器(FLASH)

高达20K字节的SRAM

时钟、复位和电源管理

− 2.0~3.6伏供电和I/O引脚

−上电/断电复位(POR/PDR)、可编程电压监测 器(PVD)

− 4~16MHz晶体振荡器 − 内嵌经出厂调校的8MHz的RC振荡器

− 内嵌带校准的40kHz的RC振荡器

− 产生CPU时钟的PLL − 带校准功能的32kHz RTC振荡器(实时通信时钟)

低功耗

− 睡眠、停机和待机模式

− VBAT为RTC和后备寄存器供电

ADC数模转换器
  • 2个12位模数转换器,1μs转换时间(多达16个 输入通道)

  • 转换范围:0至3.6V

  • 双采样和保持功能

  • 温度传感器

内嵌2个12位的模拟/数字转换器(ADC)每个ADC共用多达16个外部通道, 可以实现单次或扫描转换。在扫描模式下,自动进行在选定的一组模拟输入上的转换。 ADC接口上的其它逻辑功能包括: ● 同步的采样和保持 ● 交叉的采样和保持 ● 单次采样

ADC可以使用DMA操作。 模拟看门狗功能允许非常精准地监视一路、多路或所有选中的通道,当被监视的信号超出预置的阀 值时,将产生中断。 由标准定时器(TIMx)和高级控制定时器(TIM1)产生的事件,可以分别内部级联到ADC的开始触发和注 入触发,应用程序能使AD转换与时钟同步

DMA

联通数据传输

−7通道DMA控制器

−支持的外设:定时器、ADC、SPI、I 2 C和 USART

灵活的7路通用DMA可以管理存储器到存储器、设备到存储器和存储器到设备的数据传输;DMA控 制器支持环形缓冲区的管理,避免了控制器传输到达缓冲区结尾时所产生的中断。 每个通道都有专门的硬件DMA请求逻辑,同时可以由软件触发每个通道;传输的长度、传输的源地 址和目标地址都可以通过软件单独设置。 DMA可以用于主要的外设:SPI、I 2 C、USART,通用、基本和高级控制定时器TIMx和ADC

I/O端口

− 26/37/51/80个I/O口,所有I/O口可以映像到16个外部中断几乎所有端口均可容忍5V信号

通用输入输出接口(GPIO)

每个GPIO引脚都可以由软件配置成输出(推挽或开漏)、输入(带或不带上拉或下拉)或复用的外设功能 端口。多数GPIO引脚都与数字或模拟的复用外设共用除了具有模拟输入功能的端口,所有的GPIO 引脚都有大电流通过能力。 在需要的情况下,I/O引脚的外设功能可以通过一个特定的操作锁定,以避免意外的写入I/O寄存器。 在APB2(内部通信总线)上的I/O脚可达18MHz的翻转速度

接口定义

调试模式

STM32常用:串行单线JTAG调试口(SWJ-DP)接口

串行单线调试(SWD)和JTAG接口

定时器

3个16位定时器,每个定时器有多达4个用于 输入捕获/输出比较/PWM或脉冲计数的通道 和增量编码器输入

1个16位带死区控制和紧急刹车,用于电机 控制的PWM高级控制定时器

2个看门狗定时器(独立的和窗口型的)

系统时间定时器:24位自减型计数器

通用定时器

内置了多达3个可同步运行的标准定时器(TIM2、TIM3和TIM4)。每个 定时器都有一个16位自动加载递加/递减计数器、一个16位的预分频器4个独立的通道,每个通 道都可用于输入捕获、输出比较、PWM和单脉冲模式输出,在最大的封装配置中可提供最多12个输 入捕获、输出比较或PWM通道。 它们还能通过定时器链接功能与高级控制定时器共同工作,提供同步或事件链接功能。在调试模式 下,计数器可以被冻结。任一标准定时器都能用于产生PWM输出。每个定时器都有独立的DMA请求机制。 这些定时器还能够处理增量编码器的信号,也能处理1至3个霍尔传感器的数字输出

高级定时器

高级控制定时器(TIM1)可以被看成是分配到6个通道的三相PWM发生器,它具有带死区插入的互补 PWM输出,还可以被当成完整的通用定时器。四个独立的通道可以用于: ● 输入捕获 ● 输出比较 ● 产生PWM(边缘或中心对齐模式) ● 单脉冲输出

配置为16位标准定时器时,它与TIMx定时器具有相同的功能。配置为16位PWM发生器时,它具有全 调制能力(0~100%)。 在调试模式下,计数器可以被冻结,同时PWM输出被禁止,从而切断由这些输出所控制的开关。 很多功能都与标准的TIM定时器相同,内部结构也相同,因此高级控制定时器可以通过定时器链接功 能与TIM定时器协同操作,提供同步或事件链接功能。

看门狗定时器

cpu出现错误发现并复位

独立的看门狗

是基于一个12位的递减计数器和一个8位的预分频器,它由一个内部独立的40kHz的RC 振荡器提供时钟;因为这个RC振荡器独立于主时钟,所以它可运行于停机和待机模式。它可以被当成看门狗用于在发生问题时复位整个系统,或作为一个自由定时器为应用程序提供超时管理。通过 选项字节可以配置成是软件或硬件启动看门狗。在调试模式下,计数器可以被冻结

用作复位时钟

窗口看门狗

窗口看门狗内有一个7位的递减计数器,并可以设置成自由运行。它可以被当成看门狗用于在发生问 题时复位整个系统。它由主时钟驱动,具有早期预警中断功能;在调试模式下,计数器可以被冻结

用作中断时钟

系统时基定时器

(滴答定时器)

这个定时器是专用于实时操作系统,也可当成一个标准的递减计数器。它具有下述特性:

24位的递减计数器

自动重加载功能

当计数器为0时能产生一个可屏蔽系统中断

可编程时钟源

定时器与单片机关系

通信接口
I²C总线

I2C总线 多达2个I 2 C总线接口,能够工作于多主模式或从模式,支持标准和快速模式。 I 2 C接口支持7位或10位寻址,7位从模式时支持双从地址寻址。内置了硬件CRC发生器/校验器。 它们可以使用DMA操作并支持SMBus总线2.0版/PMBus总线。

通用同步/异步收发器(USART)

USART1接口通信速率可达4.5兆位/秒,其他接口的通信速率可达2.25兆位/秒。USART接口具有硬 件的CTS和RTS信号管理、支持IrDA SIR ENDEC传输编解码、兼容ISO7816的智能卡并提供LIN主/ 从功能。 所有USART接口都可以使用DMA操作

USART只是一种协议方式,根据不同电平方式分为RS232和RS485

串行外设接口(SPI)

多达2个SPI接口,在从或主模式下,全双工和半双工的通信速率可达18兆位/秒3位的预分频器可产生8种主模式频率,可配置成每帧8位或16位。硬件的CRC产生/校验支持基本的SD卡和TF卡模式。 所有的SPI接口都可以使用DMA操作

用于板级设备间通信,简单稳定,速度较快

控制器区域网络(CAN)

CAN接口兼容规范2.0A和2.0B(主动),位速率高达1兆位/秒。它可以接收和发送11位标识符的标准帧, 也可以接收和发送29位标识符的扩展帧。具有3个发送邮箱2个接收FIFO3级14个可调节的滤波器

用于汽车、工业的智能设备通信,通信速度快、距离远、稳定、自动差错

通用串行总线(USB)

内嵌一个兼容全速USB的设备控制器,遵循全速USB设备(12兆位/ 秒)标准,端点可由软件配置,具有待机/唤醒功能。USB专用的48MHz时钟由内部主PLL直接产生(时钟源必须是一个HSE晶体振荡器【外部高速晶振】)。

用于pc机的从设备

CRC(循环冗余校验)计算单元

使用一个固定的多项式发生器,从一个32位的数据字产生一个CRC码。在众多的应用中,基于CRC的技术被用于验证数据传输或存储的一致性。在EN/IEC 60335-1标准的范围内,它提供了一种检测闪存存储器错误的手段,CRC计算单元可以用于实时地计算软件的签名, 并与在链接和生成该软件时产生的签名对比

96位的芯片唯一代码

保护程序的不可复制、用作密码提高安全性、用作产品序列号

嵌套的向量式中断控制器(NVIC)

能够处理多达43个可屏蔽中断通道(不包括16个Cortex™-M3的中断线)和16个优先级。

● 紧耦合的NVIC能够达到低延迟的中断响应处理

● 中断向量入口地址直接进入内核

● 紧耦合的NVIC接口

● 允许中断的早期处理

● 处理晚到的较高优先级中断

● 支持中断尾部链接功能

● 自动保存处理器状态

● 中断返回时自动恢复,无需额外指令开销 该模块以最小的中断延迟提供灵活的中断管理功能

外部中断/事件控制器(EXTI)

外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每个中断线都可以独立地配置 它的触发事件(上升沿或下降沿或双边沿),并能够单独地被屏蔽;有一个挂起寄存器维持所有中断请 求的状态。EXTI可以检测到脉冲宽度小于内部APB2的时钟周期。多达80个通用I/O口连接到16个外部中断线。

自举模式

在启动时,通过自举引脚可以选择三种自举模式中的一种: ● 从程序闪存存储器自举 ● 从系统存储器自举 ● 从内部SRAM自举 自举加载程序(Bootloader)存放于系统存储器中,可以通过USART1对闪存重新编程。

时钟和启动

系统时钟的选择是在启动时进行,复位时内部8MHz的RC振荡器被选为默认的CPU时钟,随后可以 选择外部的、具失效监控的4~16MHz时钟;当检测到外部时钟失效时,它将被隔离,系统将自动地 切换到内部的RC振荡器,如果使能了中断,软件可以接收到相应的中断。同样,在需要时可以采取 对PLL时钟完全的中断管理(如当一个间接使用的外部振荡器失效时)。 多个预分频器用于配置AHB的频率、高速APB(APB2)和低速APB(APB1)区域。AHB和高速APB的最 高频率是72MHz,低速APB的最高频率为36MHz。

AHB是高级高性能总线,用于CPU、DMA、DSP通信

APB是外围总线,用于内部其他功能的通信

APB分为高速APB2和低俗APB1

总原理图

单片机开发

最小系统电路

内核、存储器、时钟、复位、电源管理(后三可外接)

启动模式

KEIL4

工程文件结构

CMSIS:内核驱动程序

Lib:内部功能的基本函数库

Startup:单片机启动程序

User:用户程序(包括主函数)

Basic:内部功能的驱动程序

Hardware:外部硬件的驱动程序

固件库

STM32F10x固件库下载与安装说明(第20步固件库的安装).pdf

STM32F103固件函数库用户手册(中文).pdf

核心板电路

LED闪灯

利用宏定义和固件库

#define LED1 PBout(0)// PB0
#define LED2 PBout(1)// PB1	

PBout(0) = 1;        //LED1 = 1;

方法1:
 	  通过枚举设置参数
 		delay_us(50000); //延时1秒
 		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
 		delay_us(50000); //延时1秒
方法2:
 		  GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //取反LED1
 		  delay_ms(500); //延时1秒
方法3:
	用函数方法设置
 		  GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1)
 		  delay_s(1); //延时1秒
 		  GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0)
 		  delay_s(1); //延时1秒
方法4
	整组置位数值
		GPIO_Write(LEDPORT,0x0001); //直接数值操作将变量值写入LED
		delay_s(2); //延时1秒
		GPIO_Write(LEDPORT,0x0000); //直接数值操作将变量值写入LED
		delay_s(2); //延时1秒

延时函数

#define AHB_INPUT  72  //请按RCC中设置的AHB时钟频率填写到这里(单位MHz)


void delay_us(u32 uS){ //uS微秒级延时程序(参考值即是延时数,72MHz时最大值233015)	
	SysTick->LOAD=AHB_INPUT*uS;      //重装计数初值(当主频是72MHz,72次为1微秒)
	SysTick->VAL=0x00;        //清空定时器的计数器
	SysTick->CTRL=0x00000005;//时钟源HCLK,打开定时器
	while(!(SysTick->CTRL&0x00010000)); //等待计数到0
	SysTick->CTRL=0x00000004;//关闭定时器
}

void delay_ms(u16 ms){ //mS毫秒级延时程序(参考值即是延时数,最大值65535)	 		  	  
	while( ms-- != 0){
		delay_us(1000);	//调用1000微秒的延时
	}
}
 
void delay_s(u16 s){ //S秒级延时程序(参考值即是延时数,最大值65535)	 		  	  
	while( s-- != 0){
		delay_ms(1000);	//调用1000毫秒的延时
	}
} 

呼吸灯

利用占空比调节led灯亮度

开的时间短关的时间长就会变暗  开的时间长关的时间短就会变亮

int main (void){//主程序
	//定义需要的变量 u8为无符号的8位变量定义
	u8 MENU;
	u16 t,i;
	//初始化程序
	RCC_Configuration(); //时钟设置
	LED_Init();
	//设置变量的初始值
	MENU = 0;
	t = 1;
	//主循环
	while(1){
		//菜单0
		if(MENU == 0){ //变亮循环
			for(i = 0; i < 10; i++){
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED1接口输出高电平1
				delay_us(t); //延时
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
				delay_us(501-t); //延时
			}
			t++;
			if(t==500){
				MENU = 1;
			}
		}
		//菜单1
		if(MENU == 1){ //变暗循环
			for(i = 0; i < 10; i++){
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED1接口输出高电平1
				delay_us(t); //延时
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
				delay_us(501-t); //延时
			}
			t--;
			if(t==1){
				MENU = 0;
			}
		}		
	}
}

变量定义

u32 a; //定义32位无符号变量a u16 a; //定义16位无符号变量a u8 a; //定义8位无符号变量a vu32 a; //定义易变的32位无符号变量a vu16 a; //定义易变的 16位无符号变量a vu8 a; //定义易变的 8位无符号变量a uc32 a; //定义只读的32位无符号变量a uc16 a; //定义只读 的16位无符号变量a uc8 a; //定义只读 的8位无符号变量a

按键控制LED
//示例1:无锁存
 		if(GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
 			GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0) 
 		}else{	
         	GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1) 
 		}
//示例2:无锁存
		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(!GPIO_ReadInputDataBit(KEYPORT,KEY1))); 
//示例3:有锁存  经典按键读取
 		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
 			delay_ms(20); //延时去抖动
 			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
 				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //LED取反
 				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 
 			}
 		}
//示例4:有锁存
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
				//在2个LED上显示二进制加法
				a++; //变量加1
				if(a>3){ //当变量大于3时清0
					a=0; 
				}
				GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上) 00 01 10 
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 

FLASH读写

整页写 整页擦

//flash写入
uint8_t US_FLASH_WRITE_BYTE(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t Num){//擦除扇区后以8位字节写入FLASH(参数:地址,数据内容,数量)
	FLASH_EraseInitTypeDef User_Flash_Erase;//声明结构体
	uint32_t SectorError=0;
	static uint32_t addrx=0;
	uint32_t endaddr=0;
	//准备工作
	if(WriteAddr < STM32_FLASH_BASE)return 1;//如地址不要FLASH地址区间,返回错误代码1
	HAL_FLASH_Unlock();//解锁FLASH
	if(FLASH_WaitForLastOperation(FLASH_WAITETIME)!=HAL_OK)return 2; //等待完成(失败:返回错误标记2)
	addrx   = WriteAddr;//写入的起始地址
	endaddr = WriteAddr + Num;//写入的结束地址
	//擦除扇区
	if(addrx < STM32_FLASH_END){//判断地址是否超出FLASH所在扇区范围
		while(addrx < endaddr){//循环擦除写入地址内不是0xFFFFFFFF的所在扇区
			if(US_FLASH_READ_WORD(addrx) != 0XFFFFFFFF){//判断当前地址内容是不是0XFFFFFFFF,如不是则擦除地址所在扇区
				User_Flash_Erase.TypeErase = FLASH_TYPEERASE_SECTORS;//擦除类型,扇区擦除
				User_Flash_Erase.Banks = FLASH_BANK_1; //FLASH所在的片选区(STM32F407只有FLASH_BANK_1)
				User_Flash_Erase.Sector = STMFLASH_GetFlashSector(addrx);//要擦除的扇区
				User_Flash_Erase.NbSectors = 1;//一次只擦除一个扇区
				User_Flash_Erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;//设置FLASH电压范围2.7~3.6V
				if(HAL_FLASHEx_Erase(&User_Flash_Erase,&SectorError) != HAL_OK)return 3;//开始擦除扇区(失败:返回错误代码3)
			}else addrx+=4;//否则地址加4(因使用32位读,所以加4)
			if(FLASH_WaitForLastOperation(FLASH_WAITETIME)!=HAL_OK)return 4; //等待完成(失败:返回错误代码4)
		}
	}
	//写入数据
	if(FLASH_WaitForLastOperation(FLASH_WAITETIME) == HAL_OK){//等待FLASH准备就绪
		 while(WriteAddr < endaddr){//循环写入,直到最后一个地址endaddr
			if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE,WriteAddr,*pBuffer)!=HAL_OK)return 5;//写入数据(失败:返回错误代码5)
			WriteAddr++;pBuffer++;//地址加1,数据内容数组加1
		}
	}
	HAL_FLASH_Lock();//锁定FLASH
	return 0;
}
//flash读取
void US_FLASH_READ_BYTE (uint32_t User_Flash_Add,uint8_t *pBuffer,uint32_t Num){ //以字节读出FLASH(参数:FLASH地址,存放数组,数量)
	uint32_t a=0;
	while(a < Num){//循环读取,直到最大数量
    	*pBuffer = *(__IO uint8_t*)(User_Flash_Add + a); //读出1个字节数据
    	pBuffer++; a++;//指针地址加1,数量加1
    }
}

蜂鸣器驱动

蜂鸣器原理

蜂鸣器定义

void BUZZER_Init(void){ //蜂鸣器的接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    GPIO_InitStructure.GPIO_Pin = BUZZER; //选择端口号                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(BUZZERPORT, &GPIO_InitStructure);	
	
	GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1		
}

蜂鸣器驱动

延时时间越短音调越高 延时时间越长音调越低

void BUZZER_BEEP1(void){ //蜂鸣器响一声
	u16 i;
	for(i=0;i<200;i++){
		GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
		delay_us(500); //延时		
		GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
		delay_us(500); //延时		
	}
}

蜂鸣器主程序

int main (void){//主程序
	u16 a; //定义变量
	//初始化程序
	RCC_Configuration(); //时钟设置
	LED_Init();//LED初始化
	KEY_Init();//按键初始化

	BUZZER_Init();//蜂鸣器初始化
	BUZZER_BEEP1();//蜂鸣器音1

    a = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH

	GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)

	//主循环
	while(1){

		//示例4:有锁存
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
				//在2个LED上显示二进制加法
				a++; //变量加1
				if(a>3){ //当变量大于3时清0
					a=0; 
				}
				GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
			
				BUZZER_BEEP1();//蜂鸣器音1

				FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASH
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
				BUZZER_BEEP2();//蜂鸣器音2
			}
		}
	}
}
MIDI音乐播放

MIDI数字音乐

延时函数决定音调 内层for循环决定长度(节拍)外层for循环代表音符

简谱

uc16 musicl[78] = {//奇数是音调 偶数是长度
	330,750,
	440,375,
	494,375,
	523,750,
	587,375,
	659,375,
	587,750,
	494,375,
	392,375,
	440,1500,
	330,750,
	440,375,
	494,375,
	523,750,
	587,375,
	659,375,
	587.750.
	494,375,
	392,375,
	784,1500,
	659,750,
	698,375,
	784,375,
	880,750,
}

播放函数

void MIDI_PLAY(void){
	u16 i,e;
	for(i=0,i<39;i++){
		for(e=0,e<musicl[i*2]*musicl[i*2+1]/1000;e++){
			GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
			delay_us(500000/musicl[i*2]); //延时		
			GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
			delay_us(500000/musicl[i*2]); //延时
		}
	}
}

USART发送程序

利用DYS串口助手接收单片机发送的数据

注意发送的都是十六进制数

while(1){

		/* 发送方法1 */
		USART_SendData(USART1 , 0x55); //发送单个数值
		while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); //检查发送中断标志位

直接可用c语言语法发送

//只有一个串口可以用printf
printf("STM32F103 "); //纯字符串发送数据到串口
printf("STM32 %d %d ",a,b); //纯字符串和变量发送数据到串口,a符号变量

用特定函数发送

/* 发送方法3 */
//		USART1_printf("STM32 %d %d ",a,b);

USART接收程序

如用查询功能 需在初始化里关闭USART_ITConfig中的ENABLE改为DISABLE

void USART1_Init(u32 bound){ //串口1初始化并启动
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
     //USART1_TX   PA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);  
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
   //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器 
   //USART 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	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(USART1, &USART_InitStructure); //初始化串口
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
    USART_Cmd(USART1, ENABLE);                    //使能串口 
}

查询模式

//查询
USART1_Init(115200); //串口初始化(参数是波特率)

	//主循环
	while(1){

		//查询方式接收
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){  //查询串口待处理标志位
			a = USART_ReceiveData(USART1);//读取接收到的数据
			printf("%c",a); //把收到的数据发送回电脑		  
		}

中断模式

利用中断程序保证数据接收后不影响后续程序

void USART1_IRQHandler(void){ //串口1中断服务程序(固定的函数名不能修改)	
	u8 a;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){  //接收中断(接收到的数据必须是0x0d 0x0a结尾)		
		a =USART_ReceiveData(USART1);//读取接收到的数据
		printf("%c",a); //把收到的数据发送回电脑		  
	} 
} 

USART控制程序

利用电脑输入控制单片机 单片机的反馈给电脑

//主循环
	while(1){

		//查询方式接收
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){  //查询串口待处理标志位
			a =USART_ReceiveData(USART1);//读取接收到的数据
			switch (a){
				case '0':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
					printf("%c:LED1 OFF ",a); //
					break;
				case '1':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
					printf("%c:LED1 ON ",a); //
					break;
				case '2':
					BUZZER_BEEP1(); //蜂鸣一声
					printf("%c:BUZZER ",a); //把收到的数据发送回电脑
					break;
				default:
					break;
			}		  
		}

		//按键控制
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 
				printf("KEY1 "); //
			}
		}		 
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按键松开 
				printf("KEY2 "); //
			}
		}

RTC时钟原理

void RTC_First_Config(void){ //首次启用RTC的设置
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR和BKP的时钟(from APB1)
    PWR_BackupAccessCmd(ENABLE);//后备域解锁
    BKP_DeInit();//备份寄存器模块复位
    RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振开启   
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待稳定    
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC时钟源配置成LSE(外部低速晶振32.768KHZ)    
    RCC_RTCCLKCmd(ENABLE);//RTC开启    
    RTC_WaitForSynchro();//开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器    
    RTC_WaitForLastTask();//读写寄存器前,要确定上一个操作已经结束
    RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)   
    RTC_WaitForLastTask();//等待寄存器写入完成	
    //当不使用RTC秒中断,可以屏蔽下面2条
//    RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断   
//    RTC_WaitForLastTask();//等待写入完成
}

时钟初始化

void RTC_Config(void){ //实时时钟初始化
    //在BKP的后备寄存器1中,存了一个特殊字符0xA5A5
    //第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//判断寄存数据是否丢失       
        RTC_First_Config();//重新配置RTC        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
    }else{
		//若后备寄存器没有掉电,则无需重新配置RTC
        //这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
            //这是上电复位
            //需要的函数
            
            
            
            
        }
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
            //这是外部RST管脚复位
            //需要的函数
            
            
            
            
        }       
        RCC_ClearFlag();//清除RCC中复位标志

        //虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行
        //但是每次上电后,还是要使能RTCCLK
        RCC_RTCCLKCmd(ENABLE);//使能RTCCLK        
        RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步

        //当不使用RTC秒中断,可以屏蔽下面2条
//        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断        
//        RTC_WaitForLastTask();//等待操作完成
    }
	#ifdef RTCClockOutput_Enable   
	    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	    PWR_BackupAccessCmd(ENABLE);   
	    BKP_TamperPinCmd(DISABLE);   
	    BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
	#endif
}

时钟中断

void RTC_IRQHandler(void){ //RTC时钟1秒触发中断函数(名称固定不可修改)
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET){
	//秒中断函数
        
        
        
	}
	RTC_ClearITPendingBit(RTC_IT_SEC); 
	RTC_WaitForLastTask();
}

void RTCAlarm_IRQHandler(void){	//闹钟中断处理(启用时必须调高其优先级)
	if(RTC_GetITStatus(RTC_IT_ALR) != RESET){
	//闹钟中断函数
        
        
        
	}
	RTC_ClearITPendingBit(RTC_IT_ALR);
	RTC_WaitForLastTask();
}

时间判断

//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year){                    
	if(year%4==0){ //必须能被4整除
		if(year%100==0){		
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除          
			else return 0;  
		}else return 1;  
	}else return 0;
}                           
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份

//月份数据表                                                                       
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表  
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表

//写入时间
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){ //写入当前时间(1970~2099年有效),
	u16 t;
	u32 seccount=0;
	if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099       
	for(t=1970;t<syear;t++){ //把所有年份的秒钟相加
		if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
		else seccount+=31536000;                    //平年的秒钟数
	}
	smon-=1;
	for(t=0;t<smon;t++){         //把前面月份的秒钟数相加
		seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数        
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
	seccount+=(u32)hour*3600;//小时秒钟数
	seccount+=(u32)min*60;      //分钟秒钟数
	seccount+=sec;//最后的秒钟加上去
	RTC_First_Config(); //重新初始化时钟
	BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
	RTC_SetCounter(seccount);//把换算好的计数器值写入
	RTC_WaitForLastTask(); //等待写入完成
	return 0; //返回值:0,成功;其他:错误代码.    
}

读出时间

//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year){                    
	if(year%4==0){ //必须能被4整除
		if(year%100==0){		
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除          
			else return 0;  
		}else return 1;  
	}else return 0;
}                           
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份

//月份数据表                                                                       
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表  
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表

//写入时间
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){ //写入当前时间(1970~2099年有效),
	u16 t;
	u32 seccount=0;
	if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099       
	for(t=1970;t<syear;t++){ //把所有年份的秒钟相加
		if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
		else seccount+=31536000;                    //平年的秒钟数
	}
	smon-=1;
	for(t=0;t<smon;t++){         //把前面月份的秒钟数相加
		seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数        
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
	seccount+=(u32)hour*3600;//小时秒钟数
	seccount+=(u32)min*60;      //分钟秒钟数
	seccount+=sec;//最后的秒钟加上去
	RTC_First_Config(); //重新初始化时钟
	BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
	RTC_SetCounter(seccount);//把换算好的计数器值写入
	RTC_WaitForLastTask(); //等待写入完成
	return 0; //返回值:0,成功;其他:错误代码.    
}

按键双击和长按
#define KEYA_SPEED1	100	  //长按的时间长度(单位10mS)
#define KEYA_SPEED2	10	  //双击的时间长度(单位20mS)


int main (void){//主程序
	u8 a=0,b,c=0;
	RCC_Configuration(); //系统时钟初始化 
	LED_Init();//LED初始化
	TOUCH_KEY_Init();//按键初始化
	while(1){

		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){ //检测按键是否按下
			delay_ms(20); //延时去抖动
			if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){//判断长短键
				while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))&&c<KEYA_SPEED1){ //循环判断长按,到时跳转
					c++;delay_ms(10); //长按判断的计时
				}
				if(c>=KEYA_SPEED1){ //长键处理
					//长按后执行的程序放到此处
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
					while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
				}else{ //单击处理
					for(b=0;b<KEYA_SPEED2;b++){//检测双击
						delay_ms(20);
						if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){
							a=1;
							//双击后执行的程序放到此处
							GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制

							while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
						}
					}
					if(a==0){ //判断单击
						//单击后执行的程序放到此处
						GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
						
					}
				}
				a=0;c=0; //参数清0
			}
		} //按键判断在此结束

	}
}

TM1640电子管驱动

数码管分为共阳、共阴

共阳用正极驱动 共阴用负极驱动

主程序

int main (void){//主程序
	u8 c=0x01;
	RCC_Configuration(); //系统时钟初始化 
	RTC_Config();  //RTC初始化
	TM1640_Init(); //TM1640初始化
	while(1){
		if(RTC_Get()==0){ //读出RTC时间
			TM1640_display(0,rday/10);	//天
			TM1640_display(1,rday%10+10);
			TM1640_display(2,rhour/10); //时
			TM1640_display(3,rhour%10+10);
			TM1640_display(4,rmin/10);	//分
			TM1640_display(5,rmin%10+10);
			TM1640_display(6,rsec/10); //秒
			TM1640_display(7,rsec%10);
			
            //加10 代表显示小数点  20 代表关闭此位
            
			TM1640_led(c); //与TM1640连接的8个LED全亮
			c<<=1; //数据左移 流水灯
			if(c==0x00)c=0x01; //8个灯显示完后重新开始
			delay_ms(125); //延时
		}
	}
}

TM1640驱动

#include "TM1640.h"
#include "delay.h"

#define DEL  1   //宏定义 通信速率(默认为1,如不能通信可加大数值)

//地址模式的设置
//#define TM1640MEDO_ADD  0x40   //宏定义	自动加一模式
#define TM1640MEDO_ADD  0x44   //宏定义 固定地址模式(推荐)

//显示亮度的设置
//#define TM1640MEDO_DISPLAY  0x88   //宏定义 亮度  最小
//#define TM1640MEDO_DISPLAY  0x89   //宏定义 亮度
//#define TM1640MEDO_DISPLAY  0x8a   //宏定义 亮度
//#define TM1640MEDO_DISPLAY  0x8b   //宏定义 亮度
#define TM1640MEDO_DISPLAY  0x8c   //宏定义 亮度(推荐)
//#define TM1640MEDO_DISPLAY  0x8d   //宏定义 亮度
//#define TM1640MEDO_DISPLAY  0x8f   //宏定义 亮度 最大

#define TM1640MEDO_DISPLAY_OFF  0x80   //宏定义 亮度 关



void TM1640_start(){ //通信时序 启始(基础GPIO操作)(低层)
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1	
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1	
	delay_us(DEL);
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0	
	delay_us(DEL);
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0	
	delay_us(DEL);
}
void TM1640_stop(){ //通信时序 结束(基础GPIO操作)(低层)
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0	
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1	
	delay_us(DEL);
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1	
	delay_us(DEL);
}
void TM1640_write(u8 date){	//写数据(低层)
	u8 i;
	u8 aa;
	aa=date;
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0	
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0	
	for(i=0;i<8;i++){
		GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0	
		delay_us(DEL);

		if(aa&0x01){
			GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1	
			delay_us(DEL);
		}else{
			GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0	
			delay_us(DEL);
		}
		GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1	
		delay_us(DEL);
		aa=aa>>1;
   }
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0	
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0	
}

void TM1640_Init(void){ //TM1640接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
    GPIO_InitStructure.GPIO_Pin = TM1640_DIN | TM1640_SCLK; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(TM1640_GPIOPORT, &GPIO_InitStructure);

	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1	
	GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1	
	TM1640_start();
	TM1640_write(TM1640MEDO_ADD); //设置数据,0x40,0x44分别对应地址自动加一和固定地址模式
	TM1640_stop();
	TM1640_start();
	TM1640_write(TM1640MEDO_DISPLAY); //控制显示,开显示,0x88,  0x89,  0x8a,  0x8b,  0x8c,  0x8d,  0x8e,  0x8f分别对应脉冲宽度为:
					 				  //------------------1/16,  2/16,  4/16,  10/16, 11/16, 12/16, 13/16, 14/16	 //0x80关显示
	TM1640_stop();	
				
}
void TM1640_led(u8 date){ //固定地址模式的显示输出8个LED控制
   TM1640_start();
   TM1640_write(TM1640_LEDPORT);	        //传显示数据对应的地址
   TM1640_write(date);	//传1BYTE显示数据
   TM1640_stop();
}
void TM1640_display(u8 address,u8 date){ //固定地址模式的显示输出
 	const u8 buff[21]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0x00};//数字0~9及0~9加点显示段码表
    //---------------   0    1    2    3    4    5    6    7    8    9    0.   1.   2.   3.   4.   5.   6.   7.   8.   9.   无   
   TM1640_start();
   TM1640_write(0xC0+address);	         //传显示数据对应的地址
   TM1640_write(buff[date]);				 //传1BYTE显示数据
   TM1640_stop();
}
void TM1640_display_add(u8 address,u8 date){	//地址自动加一模式的显示输出 
	u8 i;
 	const u8 buff[21]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0x00};//数字0~9及0~9加点显示段码表
    //---------------   0    1    2    3    4    5    6    7    8    9    0.   1.   2.   3.   4.   5.   6.   7.   8.   9.   无   
  TM1640_start();
   TM1640_write(0xC0+address);	         //设置起始地址
   for(i=0;i<16;i++){
      TM1640_write(buff[date]); 
   }
   TM1640_stop(); 
}

旋转编码器

判断向右向左转

主程序

int main (void){//主程序
	u8 a=0,b=0,c=0x01;
	RCC_Configuration(); //系统时钟初始化 
	RTC_Config();  //RTC初始化

	ENCODER_Init(); //旋转编码器初始化

	TM1640_Init(); //TM1640初始化
	TM1640_display(0,a/10); //显示数值
	TM1640_display(1,a%10);
	TM1640_display(2,20);
	TM1640_display(3,20);
	TM1640_display(4,20);
	TM1640_display(5,20);
	TM1640_display(6,20);
	TM1640_display(7,20);

	while(1){
		b=ENCODER_READ();	//读出旋转编码器值	
		if(b==1){a++;if(a>99)a=0;} //分析按键值,并加减计数器值。
		if(b==2){if(a==0)a=100;a--;}
		if(b==3)a=0;
		if(b!=0){ //如果有旋转器的操作
			TM1640_display(0,a/10); //显示数值
			TM1640_display(1,a%10);
		}

旋转编码器内部程序

u8 ENCODER_READ(void){ //接口初始化
	u8 a;//存放按键的值
	u8 kt;
	a=0;
	if(GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L))KUP=0;	//判断旋钮是否解除锁死
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&KUP==0){ //判断是否旋转旋钮,同时判断是否有旋钮锁死
		delay_us(100);
		kt=GPIO_ReadInputDataBit(ENCODER_PORT_B,ENCODER_R);	//把旋钮另一端电平状态记录
		delay_ms(3); //延时
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)){ //去抖
			if(kt==0){ //用另一端判断左或右旋转
				a=1;//右转
			}else{
				a=2;//左转
			}
			cou=0; //初始锁死判断计数器
			while(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&cou<60000){ //等待放开旋钮,同时累加判断锁死
				cou++;KUP=1;delay_us(20); //
			}
		}
	}
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)&&KUP==0){ //判断旋钮是否按下  
		delay_ms(20);
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)){ //去抖动
			a=3;//在按键按下时加上按键的状态值
			//while(ENCODER_D==0);	等等旋钮放开
		}
	}
	return a;
} 

I2C驱动

I2C驱动程序工程结构

#define I2CPORT		GPIOB	//定义IO接口
#define I2C_SCL		GPIO_Pin_6	//定义IO接口
#define I2C_SDA		GPIO_Pin_7	//定义IO接口

#define HostAddress	0xc0	//总线主机的器件地址
#define BusSpeed	200000	//总线速度(不高于400000)

void I2C_GPIO_Init(void){ //I2C接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); //启动I2C功能 
    GPIO_InitStructure.GPIO_Pin = I2C_SCL | I2C_SDA; //选择端口号                      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(I2CPORT, &GPIO_InitStructure);
}

void I2C_Configuration(void){ //I2C初始化
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_GPIO_Init(); //先设置GPIO接口的状态
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//设置为I2C模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = HostAddress; //主机地址(从机不得用此地址)
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//允许应答
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //7位地址模式
	I2C_InitStructure.I2C_ClockSpeed = BusSpeed; //总线速度设置 	
	I2C_Init(I2C1,&I2C_InitStructure);
	I2C_Cmd(I2C1,ENABLE);//开启I2C					
}

void I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite){ //I2C发送数据串(器件地址,寄存器,内部地址,数量)
	I2C_GenerateSTART(I2C1,ENABLE);//产生起始位
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);//发送器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6
	I2C_SendData(I2C1,WriteAddr); //内部功能地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//移位寄存器非空,数据寄存器已空,产生EV8,发送数据到DR既清除该事件
	while(NumByteToWrite--){ //循环发送数据	
		I2C_SendData(I2C1,*pBuffer); //发送数据
		pBuffer++; //数据指针移位
		while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除EV8
	}
	I2C_GenerateSTOP(I2C1,ENABLE);//产生停止信号
}

void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer){ //I2C发送一个字节(从地址,内部地址,内容)
	I2C_GenerateSTART(I2C1,ENABLE); //发送开始信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //等待完成	
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送从器件地址及状态(写入)
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待完成	
	I2C_SendData(I2C1,writeAddr); //发送从器件内部寄存器地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_SendData(I2C1,pBuffer); //发送要写入的内容
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_GenerateSTOP(I2C1,ENABLE); //发送结束信号
}

void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead){ //I2C读取数据串(器件地址,寄存器,内部地址,数量)
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);//开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));	//清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //写入器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除 EV6
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr); //发送读的地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //清除 EV8
	I2C_GenerateSTART(I2C1,ENABLE); //开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver); //将器件地址传出,主机为读
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //清除EV6
	while(NumByteToRead){
		if(NumByteToRead == 1){ //只剩下最后一个数据时进入 if 语句
			I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
			I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
		}
		if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)){ //读取数据
			*pBuffer = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pBuffer
			pBuffer++; //指针移位
			NumByteToRead--; //字节数减 1 
		}
	}
	I2C_AcknowledgeConfig(I2C1,ENABLE);
}

u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr){ //I2C读取一个字节
	u8 a;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); 
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
	I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
	a = I2C_ReceiveData(I2C1);
	return a;
}

寄存器地址表

I2C器件靠寄存器数据控制

寄存器操控

温度传感器温度寄存原理

温度传感器驱动程序

#define LM75A_ADD	0x9E	//器件地址

//读出LM75A的温度值(-55~125摄氏度)
//温度正负号(0正1负),温度整数,温度小数(点后2位)依次放入*Tempbuffer(十进制)
void LM75A_GetTemp(u8 *Tempbuffer){   
    u8 buf[2]; //温度值储存   
    u8 t=0,a=0;   
    I2C_READ_BUFFER(LM75A_ADD,0x00,buf,2); //读出温度值(器件地址,子地址,数据储存器,字节数)
	t = buf[0]; //处理温度整数部分,0~125度
	*Tempbuffer = 0; //温度值为正值
	if(t & 0x80){ //判断温度是否是负(MSB表示温度符号)
		*Tempbuffer = 1; //温度值为负值
		t = ~t; t++; //计算补码(原码取反后加1)
	}
	if(t & 0x01){ a=a+1; } //从高到低按位加入温度积加值(0~125)
	if(t & 0x02){ a=a+2; }
	if(t & 0x04){ a=a+4; }
	if(t & 0x08){ a=a+8; }
	if(t & 0x10){ a=a+16; }
	if(t & 0x20){ a=a+32; }
	if(t & 0x40){ a=a+64; }
	Tempbuffer++;
	*Tempbuffer = a;
	a = 0;
	t = buf[1]; //处理小数部分,取0.125精度的前2位(12、25、37、50、62、75、87)
	if(t & 0x20){ a=a+12; }
	if(t & 0x40){ a=a+25; }
	if(t & 0x80){ a=a+50; }
	Tempbuffer++;
	*Tempbuffer = a;   
}

//LM75进入掉电模式,再次调用LM75A_GetTemp();即可正常工作
//建议只在需要低功耗情况下使用
void LM75A_POWERDOWN(void){// 
    I2C_SAND_BYTE(LM75A_ADD,0x01,1); //
}

OLED屏原理

void OLED0561_Init (void){//OLED屏开显示初始化
	OLED_DISPLAY_OFF(); //OLED关显示
	OLED_DISPLAY_CLEAR(); //清空屏幕内容
	OLED_DISPLAY_ON(); //OLED屏初始值设置并开显示

}
void OLED_DISPLAY_ON (void){//OLED屏初始值设置并开显示
	u8 buf[28]={
	0xae,//0xae:关显示,0xaf:开显示
    0x00,0x10,//开始地址(双字节)       
	0xd5,0x80,//显示时钟频率?
	0xa8,0x3f,//复用率?
	0xd3,0x00,//显示偏移?
	0XB0,//写入页位置(0xB0~7)
	0x40,//显示开始线
	0x8d,0x14,//VCC电源
	0xa1,//设置段重新映射?
	0xc8,//COM输出方式?
	0xda,0x12,//COM输出方式?
	0x81,0xff,//对比度,指令:0x81,数据:0~255(255最高)
	0xd9,0xf1,//充电周期?
	0xdb,0x30,//VCC电压输出
	0x20,0x00,//水平寻址设置
	0xa4,//0xa4:正常显示,0xa5:整体点亮
	0xa6,//0xa6:正常显示,0xa7:反色显示
	0xaf//0xae:关显示,0xaf:开显示
	}; //
	I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,28);
}
void OLED_DISPLAY_OFF (void){//OLED屏关显示
	u8 buf[3]={
		0xae,//0xae:关显示,0xaf:开显示
		0x8d,0x10,//VCC电源
	}; //
	I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,3);
}
void OLED_DISPLAY_LIT (u8 x){//OLED屏亮度设置(0~255)
	I2C_SAND_BYTE(OLED0561_ADD,COM,0x81);
	I2C_SAND_BYTE(OLED0561_ADD,COM,x);//亮度值
}
void OLED_DISPLAY_CLEAR(void){//清屏操作
	u8 j,t;
	for(t=0xB0;t<0xB8;t++){	//设置起始页地址为0xB0
		I2C_SAND_BYTE(OLED0561_ADD,COM,t); 	//页地址(从0xB0到0xB7)
		I2C_SAND_BYTE(OLED0561_ADD,COM,0x10); //起始列地址的高4位
		I2C_SAND_BYTE(OLED0561_ADD,COM,0x00);	//起始列地址的低4位
		for(j=0;j<132;j++){	//整页内容填充
 			I2C_SAND_BYTE(OLED0561_ADD,DAT,0x00);
 		}
	}
}

//显示英文与数字8*16的ASCII码
//取模大小为16*16,取模方式为“从左到右从上到下”“纵向8点下高位”
void OLED_DISPLAY_8x16(u8 x, //显示汉字的页坐标(从0到7)(此处不可修改)
						u8 y, //显示汉字的列坐标(从0到63)
						u16 w){ //要显示汉字的编号
	u8 j,t,c=0;
	y=y+2; //因OLED屏的内置驱动芯片是从0x02列作为屏上最左一列,所以要加上偏移量
	for(t=0;t<2;t++){
		I2C_SAND_BYTE(OLED0561_ADD,COM,0xb0+x); //页地址(从0xB0到0xB7)
		I2C_SAND_BYTE(OLED0561_ADD,COM,y/16+0x10); //起始列地址的高4位
		I2C_SAND_BYTE(OLED0561_ADD,COM,y%16);	//起始列地址的低4位
		for(j=0;j<8;j++){ //整页内容填充
 			I2C_SAND_BYTE(OLED0561_ADD,DAT,ASCII_8x16[(w*16)+c-512]);//为了和ASII表对应要减512
			c++;}x++; //页地址加1
	}
}
//向LCM发送一个字符串,长度64字符之内。
//应用:OLED_DISPLAY_8_16_BUFFER(0," DoYoung Studio"); 
void OLED_DISPLAY_8x16_BUFFER(u8 row,u8 *str){
	u8 r=0;
	while(*str != '\0'){
		OLED_DISPLAY_8x16(row,r*8,*str++);
		r++;
    }	
}

继电器

因为PA13、PA14默认定义为JTAG接口 所以要禁用JTAG功能才能使接口作为正常输出

#define RELAYPORT	GPIOA	//定义IO接口
#define RELAY1	GPIO_Pin_14	//定义IO接口
#define RELAY2	GPIO_Pin_13	//定义IO接口

void RELAY_Init(void){ //继电器的接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能      
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//启动AFIO重映射功能时钟    
    GPIO_InitStructure.GPIO_Pin = RELAY1 | RELAY2; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(RELAYPORT, &GPIO_InitStructure);
	//必须将禁用JTAG功能才能做GPIO使用
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);// 改变指定管脚的映射,完全禁用JTAG+SW-DP
	GPIO_ResetBits(RELAYPORT,RELAY1 | RELAY2); //都为低电平(0) 初始为关继电器							
}

void RELAY_1(u8 c){ //继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	GPIO_WriteBit(RELAYPORT,RELAY1,(BitAction)(c));//通过参数值写入接口
}
void RELAY_2(u8 c){ //继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	GPIO_WriteBit(RELAYPORT,RELAY2,(BitAction)(c));//通过参数值写入接口
}

步进电机

电器原理图

四pa每一步转向90° 八pa每一步转向45°

步进电机类型

重要说明:

1.不同型号的步进电机有不同的驱动电压,使用前要确定标称电压。

2.步进电机停转时不能给线圈长时间通电,会导致电机发热损坏

3.除4步、8步驱动方式外,还可用专用步进驱动器做最大256倍的精细角度驱动

4.步进电机的扭矩(力度)时与电流相关的。旋转速度是与切换时间相关的

步进电机4拍驱动法 速度快、力小

void STEP_MOTOR_4R (u8 speed){//电机顺时针,4拍,速度快,力小
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A| STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}

void STEP_MOTOR_4L (u8 speed){//电机逆时针,4拍,速度快,力小
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}

步进电机8拍驱动法 角度小、速度慢、力大

void STEP_MOTOR_8R (u8 speed){//电机顺时针,8拍,角度小,速度慢,力大
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}

void STEP_MOTOR_8L (u8 speed){//电机逆时针,8拍,角度小,速度慢,力大
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A);//1
	delay_ms(speed); //延时
	GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
	GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
	delay_ms(speed); //延时
	STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}

RS232接口

基于USART串口

一根传输一根接收

最远通信20m

RS485接口

接受和传输在一起

最远1000m 总线结构 工业常用

CAN总线驱动

汽车领域大量使用 最远10KM

根据两条线的电压差表示1和0

CAN总线是基于相同波特率通信的

波特率 = (Pclk/((1+8+7)*9)) = 36Mhz/16/9 = 250Kbits

CAN总线连接方式

报文的种类

数据帧、遥控帧报文的格式

用户只能设置数据帧和遥控帧

过滤器种类

CAN总线收发结构

屏蔽模式

过滤器组

过滤器优先级

1、位宽为32位的过滤器,优先级高于位宽为16位的过滤器

2、对于位宽相同的过滤器,标识符列表模式的优先级高于屏蔽位模式

3、位宽和模式都相同的过滤器,优先级由过滤器号决定,过滤器号小的优先级高

其他功能

工作模式:正常、睡眠、测试

测试模式中包括:静默、环回、环回静默

时间触发通信模式

寄存器访问保护

中断

记录接收SOF时刻的时间戳

ADC驱动

用于读取线性变化的电压值

ADC特性

STM32F103单片机有2个模数转化器(ADC)

每个ADC为12位分辨率(读出数据的位长度)

2个ADC共用16个外部通道(单片机的ADC输入引脚)

2个ADC都可使用DMA进行操作(DMA可直接读取单片机数据存入内存)

ADC与DMA的关系

模拟摇杆的ADC驱动

x轴、y轴、d为按下

MP3播放原理

利用USART串口

利用MY1690芯片处理音乐文件

MY1690芯片驱动音乐

void MY1690_Init(void){ //初始化
	USART3_Init(9600);//串口3初始化并启动
	MY1690_STOP(); //上电初始化后发送一次指令激活MP3芯片
}
void MY1690_PLAY(void){ //播放
	USART3_printf("\x7e\x03\x11\x12\xef"); //其中 \x 后接十六进制数据
}
void MY1690_PAUSE(void){ //暂停
	USART3_printf("\x7e\x03\x12\x11\xef");
}
void MY1690_PREV(void){ //上一曲
	USART3_printf("\x7e\x03\x14\x17\xef");
}
void MY1690_NEXT(void){ //下一曲
	USART3_printf("\x7e\x03\x13\x10\xef");
}
void MY1690_VUP(void){ //音量加1
	USART3_printf("\x7e\x03\x15\x16\xef");
}
void MY1690_VDOWN(void){ //音量减1
	USART3_printf("\x7e\x03\x16\x15\xef");
}
void MY1690_STOP(void){ //停止
	USART3_printf("\x7e\x03\x1E\x1D\xef");
}

参数指令发送方法

void MY1690_CMD1(u8 a){	//无参数的指令发送  a操作码
	u8 i;
	i=3^a;//取校验码(异或)
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x03);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}
void MY1690_CMD2(u8 a,u8 b){ //有参数的指令发送 a操作码 b参数
	u8 i;
	i=4^a;//取校验码(异或)
	i=i^b;//取校验码(异或)
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x04);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , b);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}
void MY1690_CMD3(u8 a,u16 b){ //有参数的指令发送 a操作码 b参数(16位)
	u8 i,c,d;
	c=b/0x100; //将16位参数分成2个8位参数
	d=b%0x100;
	i=5^a;//取校验码(异或)
	i=i^c;//取校验码(异或)
	i=i^d;//取校验码(异或)
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x05);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , c);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , d);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}

语音利用录制的字有机组合,顺序排列

SPI总线原理

CH376是文件管理控制芯片,用于单片机读写U盘和TF卡中的文件

CH376
void SPI2_Init(void){ //SPI2初始化
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//使能SPI_2时钟

	GPIO_InitStructure.GPIO_Pin = SPI2_MISO;  //SPI2的MISO(PB14)为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_MOSI | SPI2_SCK;	//SPI2的MOSI(PB15)和SCLK(PB13)为复用推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_NSS;	 //SPI2的NSS(PB12)为推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线输入输出全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置为SPI的主机模式(SCK主动产生时钟)
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI数据大小:发送8位帧数据结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//空闲状态时SCK的状态,High为高电平,Low为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位,1表示在SCK的奇数沿边采样,2表示偶数沿边采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS由软件控件片选
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//时钟的预分频值(0~256)
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB高位在前
	SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC较验和的多项式
	SPI_Init(SPI2,&SPI_InitStructure); //初始化SPI2的配置项
	SPI_Cmd(SPI2,ENABLE); //使能SPI2  
}

//SPI2数据发+收程序(主要用于发送)
u8 SPI2_SendByte(u8 Byte){ //通过SPI2口发送1个数据,同时接收1个数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == RESET); //如果发送寄存器数据没有发送完,循环等待
	SPI_I2S_SendData(SPI2,Byte);  //往发送寄存器写入要发送的数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //如果接受寄存器没有收到数据,循环
	return SPI_I2S_ReceiveData(SPI2);
}

U盘读写文件

int main (void){//主程序
	u8 s,i;
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"  U DISK TEST   "); //显示字符串
	//CH376初始化	
	SPI2_Init();//SPI接口初始化
	if(mInitCH376Host()== USB_INT_SUCCESS){//CH376初始化
		OLED_DISPLAY_8x16_BUFFER(4,"   CH376 OK!    "); //显示字符串
	}
	while(1){
		while ( CH376DiskConnect( ) != USB_INT_SUCCESS ) delay_ms(100);  // 检查U盘是否连接,等待U盘拔出
		OLED_DISPLAY_8x16_BUFFER(6," U DISK Ready!  "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
		for ( i = 0; i < 100; i ++ ){ 
			delay_ms( 50 );
			s = CH376DiskMount( );  //初始化磁盘并测试磁盘是否就绪.   
			if ( s == USB_INT_SUCCESS ) /* 准备好 */
			break;                                          
			else if ( s == ERR_DISK_DISCON )/* 检测到断开,重新检测并计时 */
			break;  
			if ( CH376GetDiskStatus( ) >= DEF_DISK_MOUNTED && i >= 5 ) /* 有的U盘总是返回未准备好,不过可以忽略,只要其建立连接MOUNTED且尝试5*50mS */
			break; 
		}
		OLED_DISPLAY_8x16_BUFFER(6," U DISK INIT!   "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
      	s=CH376FileCreatePath( "/洋桃.TXT" ); // 新建多级目录下的文件,支持多级目录路径,输入缓冲区必须在RAM中 
		delay_ms(200); //每次操作后必要的延时
		s = sprintf( (char *)buf , "洋桃电子 www.DoYoung.net/YT");
		s=CH376ByteWrite( buf,s, NULL ); // 以字节为单位向当前位置写入数据块 
		delay_ms(200); //每次操作后必要的延时
		s=CH376FileClose( TRUE );   // 关闭文件,对于字节读写建议自动更新文件长度 
		OLED_DISPLAY_8x16_BUFFER(6," U DISK SUCCESS "); //显示字符串
		while ( CH376DiskConnect( ) == USB_INT_SUCCESS ) delay_ms(500);  // 检查U盘是否连接,等待U盘拔出
		OLED_DISPLAY_8x16_BUFFER(6,"                "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
	}
}

阵列键盘

原理 (端口反转方案)

PA0、PA1、PA2、PA3 做上拉电阻 4、5、6、7做GND 定位横排

然后电平反转 4、5、6、7做上拉电阻0、1、2、3做GND定位竖排

其他芯片

驱动

void KEYPAD4x4_Init(void){ //微动开关的接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);       
    GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(KEYPAD4x4PORT,&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 //推挽输出  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)     
	GPIO_Init(KEYPAD4x4PORT,&GPIO_InitStructure);
				
}
void KEYPAD4x4_Init2(void){ //微动开关的接口初始化2(用于IO工作方式反转)
	GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构	
    GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(KEYPAD4x4PORT,&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 //推挽输出  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)     
	GPIO_Init(KEYPAD4x4PORT,&GPIO_InitStructure);
				
}
u8 KEYPAD4x4_Read (void){//键盘处理函数
	u8 a=0,b=0;//定义变量
	KEYPAD4x4_Init();//初始化IO
	GPIO_ResetBits(KEYPAD4x4PORT,KEY1|KEY2|KEY3|KEY4);
	GPIO_SetBits(KEYPAD4x4PORT,KEYa|KEYb|KEYc|KEYd);
	if(!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYa) ||  	//查寻键盘口的值是否变化
		!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYb) || 
		!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYc) || 
		!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYd)){
		delay_ms (20);//延时20毫秒
		if(!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYa) ||  	//查寻键盘口的值是否变化
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYb) || 
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYc) || 
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEYd)){
	  		a = GPIO_ReadInputData(KEYPAD4x4PORT)&0xff;//键值放入寄存器a
		}
		KEYPAD4x4_Init2();//IO工作方式反转
		GPIO_SetBits(KEYPAD4x4PORT,KEY1|KEY2|KEY3|KEY4);
		GPIO_ResetBits(KEYPAD4x4PORT,KEYa|KEYb|KEYc|KEYd);
		delay_ms(10);
		b = GPIO_ReadInputData(KEYPAD4x4PORT)&0xff;//将第二次取得值放入寄存器b
		a = a|b;//将两个数据相或
		switch(a){//对比数据值
			case 0xee: b = 16; break;//对比得到的键值给b一个应用数据
			case 0xed: b = 15; break;
			case 0xeb: b = 14; break;
			case 0xe7: b = 13; break;
			case 0xde: b = 12; break;
			case 0xdd: b = 11; break;
			case 0xdb: b = 10; break;
			case 0xd7: b = 9; break;
			case 0xbe: b = 8; break;
			case 0xbd: b = 7; break;
			case 0xbb: b = 6; break;
			case 0xb7: b = 5; break;
			case 0x7e: b = 4; break;
			case 0x7d: b = 3; break;
			case 0x7b: b = 2; break;
			case 0x77: b = 1; break;
			default: b = 0; break;//键值错误处理
		}
		while(!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEY1) ||  	//等待按键放开
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEY2) || 
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEY3) || 
			!GPIO_ReadInputDataBit(KEYPAD4x4PORT,KEY4));
		delay_ms (20);//延时20毫秒
	}
return (b);//将b作为返回值
} 

外部中断原理

中断

中断是指一个突发事件,中止了cpu当前的工作,转而处理突发事件,处理完成后,再回到当前的工作继续执行。

stm32允许多种多样的中断,如外部IO、ADC、USART、IIC、USB、RTC、PVD等

stm32支持将所有的GPIO设置为中断输入

外部IO可由上沿、下沿、高低电平的三种方式触发

可选择中断或事件触发

三种方式触发

中断与事件

引脚对应的中断函数

NVIC中断初始化与函数格式

#include "NVIC.h"

u8 INT_MARK;//中断标志位

void KEYPAD4x4_INT_INIT (void){	 //按键中断初始化
	NVIC_InitTypeDef  NVIC_InitStruct;	//定义结构体变量
	EXTI_InitTypeDef  EXTI_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //启动GPIO时钟 (需要与复用时钟一同启动)     
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);//配置端口中断需要启用复用时钟

//第1个中断	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);  //定义 GPIO  中断
	
	EXTI_InitStruct.EXTI_Line=EXTI_Line4;  //定义中断线
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;              //中断使能
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;     //中断模式为 中断
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;   //下降沿触发
	
	EXTI_Init(& EXTI_InitStruct);
	
	NVIC_InitStruct.NVIC_IRQChannel=EXTI4_IRQn;   //中断线     
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;  //使能中断
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;  //抢占优先级 2
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;     //子优先级  2
	NVIC_Init(& NVIC_InitStruct);
}

void  EXTI4_IRQHandler(void){
	if(EXTI_GetITStatus(EXTI_Line4)!=RESET){//判断某个线上的中断是否发生 
		INT_MARK=1;//标志位置1,表示有按键中断
		EXTI_ClearITPendingBit(EXTI_Line4);   //清除 LINE 上的中断标志位
	}     
}
void  EXTI9_5_IRQHandler(void){
	if(EXTI_GetITStatus(EXTI_Line5)!=RESET){//判断某个线上的中断是否发生 
		INT_MARK=2;//标志位置1,表示有按键中断
		EXTI_ClearITPendingBit(EXTI_Line5);   //清除 LINE 上的中断标志位
	}     
	if(EXTI_GetITStatus(EXTI_Line6)!=RESET){//判断某个线上的中断是否发生 
		INT_MARK=3;//标志位置1,表示有按键中断
		EXTI_ClearITPendingBit(EXTI_Line6);   //清除 LINE 上的中断标志位
	}     
	if(EXTI_GetITStatus(EXTI_Line7)!=RESET){//判断某个线上的中断是否发生 
		INT_MARK=4;//标志位置1,表示有按键中断
		EXTI_ClearITPendingBit(EXTI_Line7);   //清除 LINE 上的中断标志位
	}     
}

NVIC嵌套中断控制器

抢占优先级

又称:主优先级

在嵌套时,抢占优先级较高的可以在较低的中断内嵌套中断。

同一抢占优先级不能嵌套,必须前一个中断处理完成才能进入下一个。

不同抢占优先级下,响应优先级没有意义。

响应优先级

又称:子优先级/亚优先级/次优先级/副优先级

同一抢占优先级的中断同时产生时,响应优先级较高的先处理。

同一抢占优先级不能嵌套。

数字越小优先级越大

分组

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

不同的分组决定了两种优先级的数量,用户可按自己的设计需要选择组,设置的优先级如果超出所在组的最大数量值,将会导致不可预知的错误

舵机原理

单片机向舵机PWM线发出这个波形就可控制角度,波形需要连续发送才能让舵机转动直到达到角度

PWM定时器

脉冲宽度调制器 简称脉宽调制又叫占空比

TIM3_PWM_Init(59999,23);
设置频率为50Hz
公式为:溢出时间Tout(单位秒)=(arr+1)(psc+1)/Tclk
20MS = (59999+1)*(23+1)/72000000

Tclk为通用定时器的时钟,如果APB1没有分频,则就为系统时钟,72MHZ
PWM时钟频率=72000000/(59999+1)*(23+1) = 50HZ (20ms),设置自动装载值60000,预分频系数24

ARR溢出值 决定频率

CCRx 决定占空比

psc为分频器 决定时间速度

#include "pwm.h"


void TIM3_PWM_Init(u16 arr,u16 psc){  //TIM3 PWM初始化 arr重装载值 psc预分频系数
    GPIO_InitTypeDef     GPIO_InitStrue;
    TIM_OCInitTypeDef     TIM_OCInitStrue;
    TIM_TimeBaseInitTypeDef     TIM_TimeBaseInitStrue;
    
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能TIM3和相关GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB时钟(LED在PB0引脚)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO时钟(定时器3通道3需要重映射到BP5引脚)
    
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_0;     // TIM_CH3
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;    // 复用推挽
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_50MHz;    //设置最大输出速度
    GPIO_Init(GPIOB,&GPIO_InitStrue);                //GPIO端口初始化设置
    
//    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //映射,重映射只用于64、100、144脚单片机
   //当没有重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PA6,PA7,PB0,PB1
   //当部分重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PB4,PB5,PB0,PB1 (GPIO_PartialRemap_TIM3)
   //当完全重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PC6,PC7,PC8,PC9 (GPIO_FullRemap_TIM3) 
}

 TIM_TimeBaseInitStrue.TIM_Period=arr;    //设置自动重装载值
    TIM_TimeBaseInitStrue.TIM_Prescaler=psc;        //预分频系数
    TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up;    // 计数模式  计数器向上溢出 共有五种模式
    TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1;        //时钟的分频因子,起到了一点点的延时作用,一般设为TIM_CKD_DIV1
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrue);        //TIM3初始化设置(设置PWM的周期)

TIM_OCInitStrue.TIM_OCMode=TIM_OCMode_PWM1;// PWM模式1:CNT < CCR时输出有效电平
    TIM_OCInitStrue.TIM_OCPolarity=TIM_OCPolarity_High;
// 设置极性-有效电平为:高电平
    TIM_OCInitStrue.TIM_OutputState=TIM_OutputState_Enable;// 输出使能
    TIM_OC3Init(TIM3,&TIM_OCInitStrue);        //TIM3的通道3 PWM 模式设置

    TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);        //使能预装载寄存器
    
    TIM_Cmd(TIM3,ENABLE);        //使能TIM3

TIM_OCMode_PWM1:

在向上计数时,一旦TIMx_CNT < TIMx_CCR时为有效电平,否则为无效电平

在向下计数时,一旦TIMx_CNT > TIMx_CCR时为无效电平,否则为有效电平

TIM_OCMode_PWM2:

在向上计数时,一旦TIMx_CNT < TIMx_CCR时为无效电平,否则为有效电平

在向下计数时,一旦TIMx_CNT > TIMx_CCR时为有效电平,否则为无效电平

DHT11温湿度传感器

通信数据收发

通信过程

黑色为MCU发出 黄色为DHT发出

模拟以上电平

void DHT11_IO_OUT (void){ //端口变为输出
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    GPIO_InitStructure.GPIO_Pin = DHT11_IO; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(DHT11PORT, &GPIO_InitStructure);
}

void DHT11_IO_IN (void){ //端口变为输入
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    GPIO_InitStructure.GPIO_Pin = DHT11_IO; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式       
	GPIO_Init(DHT11PORT, &GPIO_InitStructure);
}

void DHT11_RST (void){ //DHT11端口复位,发出起始信号(IO发送)
	DHT11_IO_OUT();
	GPIO_ResetBits(DHT11PORT,DHT11_IO); //	
	delay_ms(20); //拉低至少18ms						
	GPIO_SetBits(DHT11PORT,DHT11_IO); //							
	delay_us(30); //主机拉高20~40us
}

u8 Dht11_Check(void){ //等待DHT11回应,返回1:未检测到DHT11,返回0:成功(IO接收)	   
    u8 retry=0;
    DHT11_IO_IN();//IO到输入状态	 
    while (GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//DHT11会拉低40~80us
        retry++;
        delay_us(1);
    }	 
    if(retry>=100)return 1; else retry=0;
    while (!GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//DHT11拉低后会再次拉高40~80us
        retry++;
        delay_us(1);
    }
    if(retry>=100)return 1;	    
    return 0;
}

读取数据

u8 Dht11_ReadBit(void){ //从DHT11读取一个位 返回值:1/0
    u8 retry=0;
    while(GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//等待变为低电平
        retry++;
        delay_us(1);
    }
    retry=0;
    while(!GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//等待变高电平
        retry++;
        delay_us(1);
    }
    delay_us(40);//等待40us	//用于判断高低电平,即数据1或0
    if(GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO))return 1; else return 0;		   
}

u8 Dht11_ReadByte(void){  //从DHT11读取一个字节  返回值:读到的数据
    u8 i,dat;
    dat=0;
    for (i=0;i<8;i++){ 
        dat<<=1; 
        dat|=Dht11_ReadBit();
    }						    
    return dat;
}

DHT11初始化

u8 DHT11_Init (void){	//DHT11初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能      
	DHT11_RST();//DHT11端口复位,发出起始信号
	return Dht11_Check(); //等待DHT11回应
}

u8 DHT11_ReadData(u8 *h){ //读取一次数据//湿度值(十进制,范围:20%~90%) ,温度值(十进制,范围:0~50°),返回值:0,正常;1,失败 
    u8 buf[5];
    u8 i;
    DHT11_RST();//DHT11端口复位,发出起始信号
    if(Dht11_Check()==0){ //等待DHT11回应
        for(i=0;i<5;i++){//读取5位数据
            buf[i]=Dht11_ReadByte(); //读出数据
        }
        if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){	//数据校验
            *h=buf[0]; //将湿度值放入指针1
			h++;
            *h=buf[2]; //将温度值放入指针2
        }
    }else return 1;
    return 0;	    
}

主函数

if(DHT11_Init()==0){ //DHT11初始化	返回0成功,1失败
		OLED_DISPLAY_8x16_BUFFER(4,"Humidity:   %   "); //显示字符串
		OLED_DISPLAY_8x16_BUFFER(6,"Temperature:   C"); //显示字符串
	}else{
		OLED_DISPLAY_8x16_BUFFER(4,"DHT11INIT ERROR!"); //显示字符串
	}
	delay_ms(1000);//DHT11初始化后必要的延时(不得小于1秒)
	while(1){
		if(DHT11_ReadData(b)==0){//读出温湿度值  指针1是湿度 20~90%,指针2是温度 0~50C,数据为十进制
			OLED_DISPLAY_8x16(4,9*8,b[0]/10 +0x30);//显示湿度值
			OLED_DISPLAY_8x16(4,10*8,b[0]%10 +0x30);//
			OLED_DISPLAY_8x16(6,12*8,b[1]/10 +0x30);//显示温度值
			OLED_DISPLAY_8x16(6,13*8,b[1]%10 +0x30);//
		}else{
			OLED_DISPLAY_8x16_BUFFER(6,"DHT11READ ERROR!"); //显示字符串
		}
		delay_ms(1000); //延时,刷新数据的频率(不得小于1秒)
	}
}

MPU6050运动传感器

组合了陀螺仪和加速器

MPU6050驱动

void MPU6050_Init(void){  //初始化MPU6050
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_PWR_MGMT_1,0x80);//解除休眠状态
	delay_ms(1000); //等待器件就绪
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_PWR_MGMT_1,0x00);//解除休眠状态
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_SMPLRT_DIV,0x07);//陀螺仪采样率
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_CONFIG,0x06);	 
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_ACCEL_CONFIG,0x00);//配置加速度传感器工作在16G模式
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_GYRO_CONFIG,0x18);//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
}

void MPU6050_READ(u16* n){ //读出X、Y、Z三轴加速度/陀螺仪原始数据 //n[0]是AX,n[1]是AY,n[2]是AZ,n[3]是GX,n[4]是GY,n[5]是GZ 
	u8 i;
    u8 t[14]; 
    I2C_READ_BUFFER(MPU6050_ADD, MPU6050_RA_ACCEL_XOUT_H, t, 14); //读出连续的数据地址,包括了加速度和陀螺仪共12字节
    for(i=0; i<3; i++) 	//整合加速度
      n[i]=((t[2*i] << 8) + t[2*i+1]);
    for(i=4; i<7; i++)	//整合陀螺仪
      n[i-1]=((t[2*i] << 8) + t[2*i+1]);        
}

低功耗模式
  • 单片机内部功率是各功能部分功率的总和

  • 低功耗模式是通过关掉部分功能达到省电

  • STM32F103单片机共有3种低功耗模式

  • 不同模式会对系统正常工作有一定影响,需要按实际情况选择

  • 低功耗模式只针对单片机内部功能,外接电路产生的功耗不在其内

睡眠模式、停机模式、待机模式

低功耗模式

正常模式:10mA 睡眠模式:2mA 停机模式:20uA 待机模式:2uA

模式正常模式睡眠模式停机模式待机模式
耗电10mA2mA20uA2uA
优点对系统影响小节能效果好,程序不会复位最节能
缺点节能效果最差恢复时间较长程序会复位,少数条件可唤醒
适用嵌入式操作系统电池供电设备小频率使用

睡眠模式

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   SLEEP TEST   "); //显示字符串

	INT_MARK=0;//标志位清0
	NVIC_Configuration();//设置中断优先级
	KEY_INT_INIT();//按键中断初始化(PA0是按键中断输入)

	NVIC_SystemLPConfig(NVIC_LP_SEVONPEND,DISABLE);	//SEVONPEND: 0:只有使能的中断或事件才能唤醒内核。1:任何中断和事件都可以唤醒内核。(0=DISABLE,1=ENABLE) 
	NVIC_SystemLPConfig(NVIC_LP_SLEEPDEEP,DISABLE);	//SLEEPDEEP: 0:低功耗模式为睡眠模式。1:进入低功耗时为深度睡眠模式。
    NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT,DISABLE); //SLEEPONEXIT: 0: 被唤醒进入线程模式后不再进入睡眠模式。1:被唤醒后执行完相应的中断处理函数后进入睡眠模式。

	while(1){

		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
		OLED_DISPLAY_8x16_BUFFER(4,"  CPU SLEEP!    "); //显示字符串
		delay_ms(500); //

		__WFI(); //进入睡眠模式,等待中断唤醒
//		__WFE(); //进入睡眠模式,等待事件唤醒

		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
		OLED_DISPLAY_8x16_BUFFER(4,"  CPU WAKE UP!  "); //显示字符串
		delay_ms(500); //
	}

}

停机模式

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   STOP  TEST   "); //显示字符串

	INT_MARK=0;//标志位清0
	NVIC_Configuration();//设置中断优先级
	KEY_INT_INIT();//按键中断初始化(PA0是按键中断输入)
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);  //使能电源PWR时钟

	while(1){
		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
		OLED_DISPLAY_8x16_BUFFER(4,"  CPU STOP!     "); //显示字符串
		delay_ms(500); //

		PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);//进入停机模式

		RCC_Configuration(); //系统时钟初始化(停机唤醒后会改用HSI时钟,需要重新对时钟初始化) 

		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
		OLED_DISPLAY_8x16_BUFFER(4,"  CPU WAKE UP!  "); //显示字符串
		delay_ms(500); //
	}

}

待机模式

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0," STANDBY TEST   "); //显示字符串

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);  //使能电源PWR时钟
	PWR_WakeUpPinCmd(ENABLE);//WKUP唤醒功能开启(待机时WKUP脚PA0为模拟输入)

	GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
	OLED_DISPLAY_8x16_BUFFER(4,"  CPU RESET!    "); //显示字符串
	delay_ms(500); //

	GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
	OLED_DISPLAY_8x16_BUFFER(4,"   STANDBY!     "); //显示字符串
	delay_ms(500); //

	PWR_EnterSTANDBYMode();//进入待机模式

	//因为待机唤醒后程序从头运行,所以不需要加while(1)的主循环体。
}

看门狗

保证程序运行正常,提高系统稳定性

  • 看门狗是一个计数器

  • 启动后开始倒计时

  • 每过一段时间CPU要重新写入计数值(喂狗)

  • CPU能重写计数值,表示程序运行正常

  • 如果程序运行出错或死机,则不能重写计数值

  • 当计数值减到0时,看门狗会让整个单片机复位

看门狗的主要目的是监控单片机程序

如果程序不断喂狗,就证明单片机工作正常

如果程序没有喂狗,就说明单片机出了问题

看门狗不能检查问题的原因,只能通过复位单片机,让程序重新开始运行

独立看门狗

基于一个12位的递减计数器和一个8位的预分频器,由一个内部独立的40kHz的RC振荡器提供时钟,可运行于停机和待机模式。用于发生问题时复位整个系统或作为一个自由定时器为应用程序提供超时管理。

在计数到0前随时喂狗

用于监控程序是否正常运行

主程序

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化---------------"
	OLED_DISPLAY_8x16_BUFFER(0,"   IWDG TEST    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(4,"    RESET!      "); //显示字符串
	delay_ms(800); //
	OLED_DISPLAY_8x16_BUFFER(4,"                "); //显示字符串

	IWDG_Init(); //初始化并启动独立看门狗

	while(1){

		IWDG_Feed(); //喂狗

		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){
			delay_s(2);	//延时2秒,使程序不能喂狗而导致复制
		}
	}
}
//看门狗定时时间计算公式:Tout=(预分频值*重装载值)/40 (单位:ms)
//当前pre为64,rlr为625,计算得到Tout时间为1秒(大概值)。

#define pre		IWDG_Prescaler_64 //分频值范围:4,8,16,32,64,128,256
#define rlr		625 //重装载值范围:0~0xFFF(4095)

void IWDG_Init(void){ //初始化独立看门狗
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //使能对寄存器IWDG_PR和IWDG_RLR的写操作
    IWDG_SetPrescaler(pre); //设置IWDG预分频值
    IWDG_SetReload(rlr); //设置IWDG重装载值
    IWDG_ReloadCounter(); //按照IWDG重装载寄存器的值重装载IWDG计数器
    IWDG_Enable(); //使能IWDG
}

void IWDG_Feed(void){ //喂狗程序
    IWDG_ReloadCounter();//固件库的喂狗函数
}

窗口看门狗

基于7位的递减计数器,可设置自由运行。由主时钟驱动,可早期预警中断功能;在调试模式下计数器可以被冻结。

必须在规定的时间范围内喂狗

作用是监控单片机运行时效是否精确

原理

主程序

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化---------------"
	OLED_DISPLAY_8x16_BUFFER(0,"   WWDG TEST    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(4,"    RESET!      "); //显示字符串
	delay_ms(800); //
	OLED_DISPLAY_8x16_BUFFER(4,"                "); //显示字符串

	WWDG_Init(); //初始化并启动独立看门狗

	while(1){
		delay_ms(54); //用延时找到喂狗的窗口时间
		WWDG_Feed(); //喂狗

		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){
			delay_s(2);	//延时2秒,使程序不能喂狗而导致复制
		}
	}
}
//窗口看门狗定时时间计算公式:
//上窗口超时时间(单位us) = 4096*预分频值*(计数器初始值-窗口值)/APB1时钟频率(单位MHz)
//下窗口超时时间(单位us) = 4096*预分频值*(计数器初始值-0x40)/APB1时钟频率(单位MHz)

#define WWDG_CNT	0x7F //计数器初始值,范围:0x40~0x7F
#define wr		0x50 //窗口值,范围:0x40~0x7F
#define fprer	WWDG_Prescaler_8 //预分频值,取值:1,2,4,8

//如上三个值是:0x7f,0x50,8时,上窗口48MS,下窗口64MS。

void WWDG_Init(void){ //初始化窗口看门狗
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG 时钟使能
	WWDG_SetPrescaler(fprer); //设置 IWDG 预分频值
	WWDG_SetWindowValue(wr); //设置窗口值
	WWDG_Enable(WWDG_CNT); //使能看门狗,设置 counter
	WWDG_ClearFlag(); //清除提前唤醒中断标志位
	WWDG_NVIC_Init(); //初始化窗口看门狗 NVIC
	WWDG_EnableIT(); //开启窗口看门狗中断
}

void WWDG_NVIC_Init(void){ //窗口看门狗中断服务程序(被WWDG_Init调用)
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //WWDG 中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占 2 子优先级 3 组 2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //抢占 2,子优先级 3,组 2
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure); //NVIC 初始化
}

void WWDG_Feed(void){ //窗口喂狗程序
    WWDG_SetCounter(WWDG_CNT); //固件库的喂狗函数
}

void WWDG_IRQHandler(void){	//窗口看门狗中断处理程序
	WWDG_ClearFlag(); //清除提前唤醒中断标志位

	//此处加入在复位前需要处理的工作或保存数据
}

定时器

三种功能

捕获器:测量波形的频率和宽度

比较器:分为模拟比较器和输出比较器

模拟比较器:比较两组输入电压的大小

输出比较器:产生可调频率和可调占空比的脉冲波形

PWM:脉宽调制器,产生固定频率但占空比可调的脉冲波形

普通定时器:时间到时产生中断

捕获器:捕获输入接口的电平变化(上升沿或下降沿),可测量脉冲的宽度或测量脉冲频率,当接口产生上升沿或下降沿时,将当前定时器值保存

输出比较器:可输出脉冲,可调占空比和频率,主要用于步进电机、伺服电机的控制。每一个周期的长度都可以不同,每一个周期内的占空比都可以不同

定时器中断处理驱动

定时器时间计算公式Tout = ((重装载值+1)*(预分频系数+1))/时钟频率(单位为us)

//定时器时间计算公式Tout = ((重装载值+1)*(预分频系数+1))/时钟频率;
//例如:1秒定时,重装载值=9999,预分频系数=7199

void TIM3_Init(u16 arr,u16 psc){  //TIM3 初始化 arr重装载值 psc预分频系数
    TIM_TimeBaseInitTypeDef     TIM_TimeBaseInitStrue;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能TIM3
    TIM3_NVIC_Init (); //开启TIM3中断向量
	      
    TIM_TimeBaseInitStrue.TIM_Period=arr; //设置自动重装载值
    TIM_TimeBaseInitStrue.TIM_Prescaler=psc; //预分频系数
    TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up; //计数器向上溢出
    TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1; //时钟的分频因子,起到了一点点的延时作用,一般设为TIM_CKD_DIV1
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrue); //TIM3初始化设置
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//使能TIM3中断    
    TIM_Cmd(TIM3,ENABLE); //使能TIM3
}

void TIM3_NVIC_Init (void){ //开启TIM3中断向量
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;	
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x3;	//设置抢占和子优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x3;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

void TIM3_IRQHandler(void){ //TIM3中断处理函数
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){	//判断是否是TIM3中断
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

        //此处写入用户自己的处理程序
		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //取反LED1
    }
}
int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化---------------"
	OLED_DISPLAY_8x16_BUFFER(0,"   TIM TEST     "); //显示字符串

	TIM3_Init(9999,7199);//定时器初始化,定时1秒(9999,7199)

	while(1){

	//写入用户的程序
	//LED1闪烁程序在TIM3的中断处理函数中执行


	}
}

CRC校验

检验过程

程序示例

int main (void){//主程序
	u32 a,b;
	u8 c;
	u32 y[3]={0x87654321,0x98765432,0x09876543};
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化
	LED_Init();//LED 
	KEY_Init();//KEY

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化---------------"
	OLED_DISPLAY_8x16_BUFFER(0,"   CRC TEST     "); //显示字符串

	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);//开启CRC时钟

	while(1){
		CRC_ResetDR();//复位CRC,需要清0重新计算时先复位
		CRC_CalcCRC(0x12345678);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
		CRC_CalcCRC(0x23456789);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
		a = CRC_CalcCRC(0x34567890);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果

		CRC_ResetDR();//复位CRC,需要清0重新计算时先复位
		b = CRC_CalcBlockCRC(y,3);//CRC计算一个32位数组。参数:32位数组名,数组长度。返回值:32位计算结果

		CRC_SetIDRegister(0x5a);//向独立寄存器CRC_IDR写数据。参数:8位数据。
		c = CRC_GetIDRegister();//从独立寄存器CRC_IDR读数据。返回值:8位数据。

		//此时,a存放的是3个独立数的CRC结果。(32位)
		//b存放的是数组y中3个数据CRC计算结果。(32位)
		//c存放的是我们写入的独立寄存器数据0x5a。(8位)
	}
}

以下是CRC固件库函数,可在主程序中直接调用:

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);开启CRC时钟,主程序开始时调用 CRC_ResetDR();复位CRC,需要清0重新计算时先复位

uint32_t CRC_CalcCRC(uint32_t Data);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果

uint32_t CRC_CalcBlockCRC(uint32_t pBuffer[], uint32_t BufferLength);//CRC计算一个32位数组。参数:32位数组名,数组长度。返回值:32位计算结果

uint32_t CRC_GetCRC(void);//从CRC中读出计算结果。返回值:32位计算结果。

void CRC_SetIDRegister(uint8_t IDValue);//向独立寄存器CRC_IDR写数据。参数:8位数据。

uint8_t CRC_GetIDRegister(void);//从独立寄存器CRC_IDR读数据。返回值:8位数据。

芯片ID
  • 96位ID编码

  • 可读出3个32位数据,或8个8位数据

  • 可以以字节(8位)为单位读取,也可以以半字(16位)或者全字(32位)读取

  • 每个芯片编码是唯一的,出厂时固化,不可修改

  • 可用于产品序列号

  • 用来作为密码,提高安全性

  • 用来保护程序的不可复制

读出芯片ID

ID[0] = *(__IO u32 *)(0X1FFFF7E8); //读出3个32位ID 高字节
	ID[1] = *(__IO u32 *)(0X1FFFF7EC); // 
	ID[2] = *(__IO u32 *)(0X1FFFF7F0); // 低字节

	printf("ChipID: %08X %08X %8X \r\n",ID[0],ID[1],ID[2]); //从串口输出16进制ID

	if(ID[0]==0x066EFF34 && ID[1]==0x3437534D && ID[2]==0x43232328){ //检查ID是否匹配
		printf("chipID OK! \r\n"); //匹配
	}else{
		printf("chipID error! \r\n"); //不同
	}

  • 32
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值