STM32 ---- 02 再次学习32,常用算法,环境配置细节

目录

ADC模数转换

原理图、工作原理方等

ADC代码

 AD单通道

AD多通道 

DMA 直接内存搬运小助手

地址分区显示和const关键字用法

 DMA代码段

DMA多次搬运代码

extern用法

ADC + DMA多通道搬运

ADC + DMA 多通道搬运 连续循环转换

UART串口

串口硬件和软件通识 

 提取数字小技巧

 解决串口输出中文乱码问题

 代码段 ---- 串口发送

代码段 ---- 串口接收单个数据

代码段 ---- 串口接收中断 实现收和发

代码段 ---- 串口收发数据包并OLED显示 

I2C传输

基础通识

MPU-6050介绍 

​编辑 ​编辑代码段 ---- I2C软件模拟

 I2C硬件资源

硬件I2C

硬件I2C ---- 代码段

kile中无法提示补全信息如何设置 请看

思维方法 通过地址传递保存共用数据

函数传参 防止死循环


ADC模数转换

原理图、工作原理方等

 GPIO采集数据可以通过规则通道(16位每次一个菜 会覆盖可以通过DMA传输数据)和注入通道每次四个菜、搭配时钟、电源、中断、看门狗等

 配置图

 

 

 

ADC代码

 
 

void ADC_RegularChannelConfig(ADC_HandleTypeDef *hadc, uint8_t Channel, uint8_t Rank, uint8_t SamplingTime);

参数说明:

  • hadc:指向ADC句柄的指针,该句柄包含了ADC的配置信息和状态。
  • Channel:要配置的通道号。例如,如果要配置ADC1的第0个通道,则传入0。
  • Rank:通道的优先级。较高的值表示较高的优先级。如果多个通道具有相同的优先级,则按照它们在配置数组中的顺序进行采样。
  • SamplingTime:采样时间。这决定了ADC从通道读取数据所需的时间。可选的值包括:ADC_SAMPLETIME_1CYCLE_5ADC_SAMPLETIME_7CYCLES_5ADC_SAMPLETIME_13CYCLES_5ADC_SAMPLETIME_28CYCLES_5ADC_SAMPLETIME_41CYCLES_5ADC_SAMPLETIME_71CYCLES_5ADC_SAMPLETIME_239CYCLES_5

ADC_InitTypeDef 是一个结构体类型,用于定义 ADC(模拟数字转换器)的初始化参数。它通常包含以下成员变量:

  • ADC_Mode:指定 ADC 的工作模式,例如单次转换、连续转换等。
  • ADC_ScanConvMode:指定 ADC 扫描模式,例如单次扫描、连续扫描等。
  • ADC_ContinuousConvMode:指定 ADC 连续转换模式,例如单次转换、连续转换等。
  • ADC_ExternalTrigConvEdge:指定外部触发信号的边沿,例如上升沿或下降沿。
  • ADC_DataAlign:指定 ADC 数据的对齐方式,例如右对齐或左对齐。
  • ADC_NbrOfChannel:指定要配置的 ADC 通道数量。
  • ADC_ClockPrescaler:指定 ADC 时钟预分频器的值。
  • ADC_Resolution:指定 ADC 分辨率,例如 12位、10位等。
  • ADC_SamplingTime:指定 ADC 采样时间,例如 55.5ns、71.1ns等。

这些成员变量可以根据具体的硬件平台和需求进行设置,以实现所需的 ADC 功能

这是一个关于ADC(模拟数字转换器)模式的宏定义。根据这些宏定义,可以设置不同的ADC模式。以下是每个模式的含义:

  1. ADC_Mode_Independent:独立模式,不使用外部触发信号进行采样。
  2. ADC_Mode_RegInjecSimult:寄存器注入和模拟多通道模式。
  3. ADC_Mode_RegSimult_AlterTrig:寄存器模拟多通道模式,并使用外部触发信号进行采样。
  4. ADC_Mode_InjecSimult_FastInterl:注入和模拟多通道快速中断模式。
  5. ADC_Mode_InjecSimult_SlowInterl:注入和模拟多通道慢速中断模式。
  6. ADC_Mode_InjecSimult:注入和模拟多通道模式。
  7. ADC_Mode_RegSimult:寄存器模拟多通道模式。
  8. ADC_Mode_FastInterl:快速中断模式。
  9. ADC_Mode_SlowInterl:慢速中断模式。
  10. ADC_Mode_AlterTrig:使用外部触发信号进行采样的模式。

在配置ADC_Init时,需要根据具体需求和硬件平台设置一些参数。这些参数可能包括:

  1. ADC_Mode:这个参数决定了ADC的工作模式,可以是独立模式、注入序列模式、扫描模式等。例如,如果设置为STM32的ADC_Mode_Independent,则双ADC不能同步,每个ADC接口独立工作。

  2. ADC_ScanConvMode:此参数决定了ADC的扫描转换方式,可以是单次扫描模式或连续扫描模式。

  3. ADC_ContinuousConvMode:决定是否启用连续转换模式。当需要实时连续转换时可以设置为ENABLE,但这可能会消耗更多的CPU资源。

  4. ADC_ExternalTrigConv:此参数用于指定是否使用外部触发信号来启动转换。

  5. ADC_NbrOfChannel:这个参数决定了规则转换的ADC通道的数量。

  6. ADC_InitStructure.ADC_DataAlign:此参数决定了数据对齐方式,可以是左对齐或右对齐。

  7. ADC_InitStructure.ADC_Resolution:此参数决定了ADC的分辨率。

  8. ADC_InitStructure.ADC_ContinuousConvMode:决定是否启用连续转换模式。

  9. ADC_InitStructure.ADC_ExternalTrigConv:此参数用于指定是否使用外部触发信号来启动转换。

这些函数是用于ADC(模数转换器)的校准和状态查询的。下面是每个函数的功能解释:

  1. void ADC_ResetCalibration(ADC_TypeDef* ADCx);:这个函数用于重置ADC的校准寄存器,以便进行新的校准。它接受一个指向ADC实例的指针作为参数。

  2. FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);:这个函数用于获取ADC的重置校准状态。它返回一个标志状态,指示是否成功完成了重置校准。它接受一个指向ADC实例的指针作为参数。

  3. void ADC_StartCalibration(ADC_TypeDef* ADCx);:这个函数用于开始ADC的校准过程。它接受一个指向ADC实例的指针作为参数。

  4. FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);:这个函数用于获取ADC的校准状态。它返回一个标志状态,指示是否成功完成了校准。它接受一个指向ADC实例的指针作为参数。

这些函数通常在嵌入式系统中使用,用于配置和控制ADC模块的校准过程。

 AD单通道
/*
ADC采集电位器
*/
#include "stm32f10x.h"                  // Device header

//ADC初始化
void AD_Init(void)
{
	//配置三个时钟 GPIO、ADC、
	RCC_ADCCLKConfig(RCC_PCLK2_Div6); //	72MHz/6 = 12MHz 因为ADC最大不能超过14MHz
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//ADC1通道0 对应PA0
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//选择AD通道
	/*
	ADC句柄指针
	ADC通道几 对应引脚几
	通道优先级 多个通道时按照优先级顺序才有 可配置多个
	采样时间
	*/
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	//ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	
	
	//ADC初始化
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC是独立还是双ADC模式 此为独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐 为右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC触发方式 触发源 内部软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续还是单次 DISABLE则时单次
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;		//扫描还是非扫描 	DISABLE为非扫描
	ADC_InitStructure.ADC_NbrOfChannel = 1;			//转换通道数量 从第几个开始 单次则从这个开始  多次也是这个开始
	
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//ADC使能
	
	ADC_Cmd(ADC1,ENABLE);
	
	//校准 calibration 校准 标准
	ADC_ResetCalibration(ADC1);//用于重置ADC的校准寄存器,以便进行新的校准 返回1
	//用于获取ADC的重置校准状态 如果前面校准为1 则完成校准循环等待 完成后变0跳出循环
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);//用于开始ADC的校准过程
	while(ADC_GetCalibrationStatus(ADC1) == SET);	
}


//开启软件ADC检测并返回
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC软件触发adc1
	//采样加转换时间 除以55.5+12.5 = 68   68/12000000 = 5.6us  每次转换所需时间
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//查询ADC转换标志状态 未完成RESET 则循环等待
	return ADC_GetConversionValue(ADC1);//用于获取ADC转换结果的函数
}
#ifndef _AD_H
#define _AD_H


//ADC初始化
void AD_Init(void);

//开启软件ADC检测并返回
uint16_t AD_GetValue(void);

#endif


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t ADValue;
float valtage;//电压

int  main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"ADValue:");
	OLED_ShowString(2,1,"valtage:0.00v");
	while(1)
	{
		ADValue = AD_GetValue();
		valtage = (float)ADValue / 4095 * 3.3;//转换成电压 
		
		OLED_ShowNum(1,9,ADValue,4); 
		OLED_ShowNum(2,9,valtage,1);		  //显示电压最高位1位
		OLED_ShowNum(2,11,(uint16_t)(valtage * 100) % 100,2);//通过1.23变大100倍取余 取得后面两位显示
		Delay_ms(100);
	}
}

AD多通道 
#include "stm32f10x.h"                  // Device header

//ADC初始化
void AD_Init(void)
{
	//配置三个时钟 GPIO、ADC、
	RCC_ADCCLKConfig(RCC_PCLK2_Div6); //	72MHz/6 = 12MHz 因为ADC最大不能超过14MHz
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//ADC1通道0 对应PA0
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

	//ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	
	
	//ADC初始化
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC是独立还是双ADC模式 此为独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐 为右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC触发方式 触发源 内部软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续还是单次 DISABLE则时单次
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;		//扫描还是非扫描 	DISABLE为非扫描
	ADC_InitStructure.ADC_NbrOfChannel = 1;			//转换通道数量 从第几个开始 单次则从这个开始  多次也是这个开始
	
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//ADC使能
	
	ADC_Cmd(ADC1,ENABLE);
	
	//校准 calibration 校准 标准
	ADC_ResetCalibration(ADC1);//用于重置ADC的校准寄存器,以便进行新的校准 返回1
	//用于获取ADC的重置校准状态 如果前面校准为1 则完成校准循环等待 完成后变0跳出循环
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);//用于开始ADC的校准过程
	while(ADC_GetCalibrationStatus(ADC1) == SET);	
}


//开启软件ADC检测并返回 并配置AD通道 传入通道
uint16_t AD_GetValue(uint8_t ADC_ChanneX)
{
	
		//选择AD通道
	/*
	ADC句柄指针
	ADC通道几 对应引脚几
	通道优先级 多个通道时按照优先级顺序才有 可配置多个
	采样时间
	*/
	ADC_RegularChannelConfig(ADC1,ADC_ChanneX,1,ADC_SampleTime_55Cycles5);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC软件触发adc1
	//采样加转换时间 除以55.5+12.5 = 68   68/12000000 = 5.6us  每次转换所需时间
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//查询ADC转换标志状态 未完成RESET 则循环等待
	return ADC_GetConversionValue(ADC1);//用于获取ADC转换结果的函数
}

#ifndef _AD_H
#define _AD_H


//ADC初始化
void AD_Init(void);

//开启软件ADC检测并返回
uint16_t AD_GetValue(uint8_t ADC_ChanneX);

#endif


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD0,AD1,AD2,AD3;
float valtage;//电压

int  main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
        //把ADC通道传过去 并返回AD通道获取得值
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);
		Delay_ms(100);
	}
}

DMA 直接内存搬运小助手

 

 

 

地址分区显示和const关键字用法

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"


/*
const 用于修饰变量,一般只能读 不能写 和flash一样
所以一般不怎么变动的常量,且占内存较大时 用const修饰 节省SRAM内存
编译后代码一般放在flash中
*/
const uint8_t aa = 0x66;

int  main(void)
{
	OLED_Init();
	OLED_ShowNum(1,1,aa,4);
//	OLED_ShowHexNum(2,1,(uint32_t)&aa,8);//地址0x20000000
	OLED_ShowHexNum(2,1,(uint32_t)&aa,8);//加上const 地址0x800ED000	
	                
	OLED_ShowHexNum(3,1,(uint32_t)&ADC1->DR,8);//外设ADC1寄存器地址4开头 4001244C
	OLED_ShowHexNum(4,1,(uint32_t)&DMA1->ISR,8);//0x40020000
	while(1)
	{
		
	}
}
  1. DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx):此函数用于取消初始化指定的DMA通道。在调用此函数后,该通道将不再使用,可以重新配置或释放资源。

  2. DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct):此函数用于初始化指定的DMA通道。它接受一个指向DMA通道的指针和一个指向DMA初始化结构体的指针作为参数。通过这个函数,你可以设置DMA通道的各种属性,如传输模式、数据大小、优先级等。

  3. DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct):此函数用于初始化DMA初始化结构体。在调用此函数后,你可以使用它来设置DMA通道的各种属性。

  4. DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState):此函数用于启动或停止指定的DMA通道。它接受一个指向DMA通道的指针和一个表示新状态的枚举值作为参数。如果新状态为ENABLE,则启动DMA通道;如果新状态为DISABLE,则停止DMA通道。

  5. DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState):此函数用于配置指定DMA通道的中断。它接受一个指向DMA通道的指针、一个表示中断类型的枚举值和一个表示新状态的枚举值作为参数。通过这个函数,你可以设置DMA通道何时触发中断。

  6. DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber):此函数用于设置指定DMA通道的当前数据计数器值。它接受一个指向DMA通道的指针和一个表示数据数量的无符号整数作为参数。通过这个函数,你可以控制DMA通道传输的数据量。

  7. uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx):此函数用于获取指定DMA通道的当前数据计数器值。它接受一个指向DMA通道的指针作为参数,并返回一个表示数据数量的无符号整数。通过这个函数,你可以查询DMA通道传输的数据量。

  8. FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG):此函数用于获取指定标志的状态。它接受一个表示标志的无符号整数作为参数,并返回一个表示标志状态的枚举值。通过这个函数,你可以检查DMA通道是否触发了某个事件。

  9. void DMA_ClearFlag(uint32_t DMAy_FLAG):此函数用于清除指定标志的状态。它接受一个表示标志的无符号整数作为参数。通过这个函数,你可以重置DMA通道的事件状态。

  10. ITStatus DMA_GetITStatus(uint32_t DMAy_IT):此函数用于获取指定中断的状态。它接受一个表示中断的无符号整数作为参数,并返回一个表示中断状态的枚举值。通过这个函数,你可以检查DMA通道是否触发了某个中断。

  11. void DMA_ClearITPendingBit(uint32_t DMAy_IT):此函数用于清除指定中断的标志位。它接受一个表示中断的无符号整数作为参数。通过这个函数,你可以重置DMA通道的中断状态。

 

DMA_InitTypeDef 是一个结构体,用于初始化 DMA 控制器。它包含了以下参数:

  1. DMA_PeripheralBaseAddr:指定 DMAy Channelx 的外设基地址。
  2. DMA_MemoryBaseAddr:指定 DMAy Channelx 的内存基地址。
  3. DMA_DIR:指定 DMA 传输方向,可以是 DMA_PERIPHERAL_TO_MEMORY(外设到内存)或 DMA_MEMORY_TO_PERIPHERAL(内存到外设)。
  4. DMA_BufferSize:指定 DMA 缓冲区的大小,单位为字节。
  5. DMA_PeripheralInc:指定外设地址增量,可以是 DMA_PERIPHERAL_INCREASE(外设地址自增)或 DMA_PERIPHERAL_NO_INCREASE(外设地址不变)。
  6. DMA_MemoryInc:指定内存地址增量,可以是 DMA_MEMORY_INCREASE(内存地址自增)或 DMA_MEMORY_NO_INCREASE(内存地址不变)。
  7. DMA_PeripheralDataSize:指定外设接收或发送的数据大小,可以是 DMA_PERIPHERAL_DATA_SIZE_BYTE(一个字节)、DMA_PERIPHERAL_DATA_SIZE_HALFWORD(半个字)、DMA_PERIPHERAL_DATA_SIZE_WORD(一个字)等。
  8. DMA_MemoryDataSize:指定内存接收或发送的数据大小,可以是 DMA_MEMORY_DATA_SIZE_BYTE(一个字节)、DMA_MEMORY_DATA_SIZE_HALFWORD(半个字)、DMA_MEMORY_DATA_SIZE_WORD(一个字)等。
  9. DMA_Mode:指定 DMA 工作模式,可以是 DMA_NORMAL(正常模式)、DMA_CIRCULAR(循环模式)等。
  10. DMA_Priority:指定 DMA 传输优先级,可以是 DMA_PRIORITY_LOW(低优先级)、DMA_PRIORITY_MEDIUM(中优先级)、DMA_PRIORITY_HIGH(高优先级)等。

 DMA代码段

#include "stm32f10x.h"                  // Device header

//DMA初始化 传入数组a地址 数组b地址 几个
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint32_t Size)
{
	//RCC DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

	
	//DMA初始化
	DMA_InitTypeDef DMA_InitStructure;	
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//源地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//源地址大小为一个字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//源地址偏移
	
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//目标基地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //目标大小为一个字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目标地址是否偏移
	
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//指定优先级为中等
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA工作模式 传输完一次停止
	DMA_InitStructure.DMA_BufferSize = Size;	//传输大小
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为源SRC  ----》到内存
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//软件触发

	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	
	//开启DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
}

#ifndef _MYDMA_H
#define _MYDMA_H
//DMA初始化
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint32_t size);

#endif




#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t BuffA[] = {0x11,0x22,0x33,0x44};
uint8_t BuffB[] = {0x00,0x00,0x00,0x00};

int  main(void)
{
	OLED_Init();
	OLED_ShowNum(1,1,BuffA[0],2);
	OLED_ShowNum(1,4,BuffA[1],2);
	OLED_ShowNum(1,7,BuffA[2],2);
	OLED_ShowNum(1,10,BuffA[3],2);
	
	OLED_ShowNum(2,1,BuffB[0],2);
	OLED_ShowNum(2,4,BuffB[1],2);
	OLED_ShowNum(2,7,BuffB[2],2);
	OLED_ShowNum(2,10,BuffB[3],2);
	
	MyDMA_Init((uint32_t)BuffA,(uint32_t)BuffB,4);
	
	OLED_ShowNum(3,1,BuffA[0],2);
	OLED_ShowNum(3,4,BuffA[1],2);
	OLED_ShowNum(3,7,BuffA[2],2);
	OLED_ShowNum(3,10,BuffA[3],2);
	
	OLED_ShowNum(4,1,BuffB[0],2);
	OLED_ShowNum(4,4,BuffB[1],2);
	OLED_ShowNum(4,7,BuffB[2],2);
	OLED_ShowNum(4,10,BuffB[3],2);
	
	while(1)     
	{
		
	}
}

DMA多次搬运代码

#include "stm32f10x.h"                  // Device header

uint32_t  MyDMA_Size;

//DMA初始化 传入数组a地址 数组b地址 几个
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint32_t Size)
{
	MyDMA_Size = Size;
	//RCC DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

	
	//DMA初始化
	DMA_InitTypeDef DMA_InitStructure;	
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//源地址 外设--》内存
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//源地址大小为一个字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//源地址偏移
	
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//目标基地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //目标大小为一个字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目标地址是否偏移
	
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//指定优先级为中等
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA工作模式 传输完一次停止
	DMA_InitStructure.DMA_BufferSize = Size;	//传输大小
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为源SRC  ----》到内存
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//软件触发

	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	
	//开启DMA
	DMA_Cmd(DMA1_Channel1,DISABLE);
}


//DMA转移(trabsfer)  
//关闭DMA使能,设置DMA搬运次数、再次开启、while循环等待转换完成、清楚标志位
void DMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//通过全局变量设置DMA当前数据计数器
	DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA
	
	//获取DMA标志位状态的函数 DMA1_FLAG_TC1为转移完成标志 该状态完成为1SET 未完成是为 RESET循环等待 完成跳出循环
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

#ifndef _MYDMA_H
#define _MYDMA_H
//DMA初始化
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint32_t size);

//DMA转移(trabsfer)  多次调用
void DMA_Transfer(void);
#endif


=================================================

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t BuffA[] = {0x11,0x22,0x33,0x44};
uint8_t BuffB[] = {0x00,0x00,0x00,0x00};

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

const uint8_t BuffA[] = {0x11,0x22,0x33,0x44};//放在flash中 不能改变地址0x8。。。。
uint8_t BuffB[] = {0x00,0x00,0x00,0x00};

int  main(void)
{
	OLED_Init();	
	MyDMA_Init((uint32_t)BuffA,(uint32_t)BuffB,4);
	
	OLED_ShowString(1,1,"BuffA");
	while(1)     
	{
		OLED_ShowHexNum(1,8,(uint32_t)BuffA,8);
		OLED_ShowNum(2,1,BuffA[0],2);
		OLED_ShowNum(2,4,BuffA[1],2);
		OLED_ShowNum(2,7,BuffA[2],2);
		OLED_ShowNum(2,10,BuffA[3],2);
		
		OLED_ShowHexNum(3,8,(uint32_t)BuffB,8);
		OLED_ShowString(3,1,"BuffB");
		OLED_ShowNum(4,1,BuffB[0],2);
		OLED_ShowNum(4,4,BuffB[1],2);
		OLED_ShowNum(4,7,BuffB[2],2);
		OLED_ShowNum(4,10,BuffB[3],2);
		
		//DMA搬运
		DMA_Transfer();	  
		
		Delay_s(1);
	}
}


extern用法

extern关键字应该加在需要使用该变量或函数的文件中,而不是定义该变量或函数的文件中。

extern关键字应该加在需要使用该变量或函数的文件中,而不是定义该变量或函数的文件中。 

ADC + DMA多通道搬运

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

//ADC初始化 DMA初始化开启搬运
void AD_Init(void)
{
	//配置四个时钟 GPIO、ADC、
	RCC_ADCCLKConfig(RCC_PCLK2_Div6); //	72MHz/6 = 12MHz 因为ADC最大不能超过14MHz
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	

	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//ADC1通道0 对应PA0
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//四个ADC通道相当于四个菜
	//ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);

	
	//ADC初始化
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC是独立还是双ADC模式 此为独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐 为右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC触发方式 触发源 内部软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续还是单次 DISABLE则时单次
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;		//扫描还是非扫描 	DISABLE为非扫描 ENABLE扫描模式多个菜
	ADC_InitStructure.ADC_NbrOfChannel = 4;			//转换通道数量 从第几个开始 单次则从这个开始  多次也是这个开始
	
	ADC_Init(ADC1,&ADC_InitStructure);
	
	
/*DMA代码块*/

	//RCC DMA时钟 放在前面了
	
	//DMA初始化
	DMA_InitTypeDef DMA_InitStructure;	
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//源地址 为ADC1的数据寄存器 外设--》内存
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//源地址大小为寄存器低十六位
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//源地址为寄存器地址 不发生偏移
	
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//目标基地址  数组名即地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //目标大小为半字 寄存器低16位和源一样
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目标地址是否偏移 收到一个数据偏移一次
	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为源SRC  ----》到内存
	DMA_InitStructure.DMA_BufferSize = 4;	//传输大小 四个通道四个菜
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA工作模式 DMA_Mode_Normal 传输完一次停止  DMA_Mode_Circular从源地址开始传输数据,直到目标地址被覆盖
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//软件触发ENABLE 硬件触发位Disable	
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//指定优先级为中等

	DMA_Init(DMA1_Channel1,&DMA_InitStructure);

	//开启DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	
/*DMA代码块*/
	
	//开启ADC_DMA搬运功能
	ADC_DMACmd(ADC1,ENABLE);
	
	//ADC使能
	ADC_Cmd(ADC1,ENABLE);

	ADC_ResetCalibration(ADC1);
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}


//开启软件ADC检测并返回 并配置AD通道 传入通道
void AD_GetValue(void)
{
	DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
	DMA_SetCurrDataCounter(DMA1_Channel1,4);//通过全局变量设置DMA当前数据计数器
	DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA

	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC软件触发adc1

	//获取DMA标志位状态的函数 DMA1_FLAG_TC1为转移完成标志 该状态完成为1SET 未完成是为 RESET循环等待 完成跳出循环
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}
#ifndef _AD_H
#define _AD_H

extern uint16_t AD_Value[4];

//ADC初始化
void AD_Init(void);

//开启软件ADC检测并返回
void AD_GetValue(void);

#endif


======================================

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"


int  main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
		AD_GetValue();
		OLED_ShowNum(1,5,AD_Value[0],4);
		OLED_ShowNum(2,5,AD_Value[1],4);
		OLED_ShowNum(3,5,AD_Value[2],4);
		OLED_ShowNum(4,5,AD_Value[3],4);

		Delay_ms(100);
	}
}


ADC + DMA 多通道搬运 连续循环转换

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

//ADC初始化 DMA初始化开启搬运
void AD_Init(void)
{
	//配置四个时钟 GPIO、ADC、
	RCC_ADCCLKConfig(RCC_PCLK2_Div6); //	72MHz/6 = 12MHz 因为ADC最大不能超过14MHz
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	

	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//ADC1通道0 对应PA0
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//四个ADC通道相当于四个菜
	//ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);

	
	//ADC初始化
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC是独立还是双ADC模式 此为独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐 为右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC触发方式 触发源 内部软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续还是单次 DISABLE则时单次 ADC连续ENABLE
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;		//扫描还是非扫描 	DISABLE为非扫描 ENABLE扫描模式多个菜
	ADC_InitStructure.ADC_NbrOfChannel = 4;			//转换通道数量 从第几个开始 单次则从这个开始  多次也是这个开始
	
	ADC_Init(ADC1,&ADC_InitStructure);
	
	
/*DMA代码块*/

	//RCC DMA时钟 放在前面了
	
	//DMA初始化
	DMA_InitTypeDef DMA_InitStructure;	
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//源地址 为ADC1的数据寄存器 外设--》内存
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//源地址大小为寄存器低十六位
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//源地址为寄存器地址 不发生偏移
	
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//目标基地址  数组名即地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //目标大小为半字 寄存器低16位和源一样
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目标地址是否偏移 收到一个数据偏移一次
	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为源SRC  ----》到内存
	DMA_InitStructure.DMA_BufferSize = 4;	//传输大小 四个通道四个菜
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA工作模式 DMA_Mode_Normal 传输完一次停止非循环  DMA_Mode_Circular 循环模式
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//软件触发ENABLE 硬件触发位Disable	
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//指定优先级为中等

	DMA_Init(DMA1_Channel1,&DMA_InitStructure);

	//开启DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	
/*DMA代码块*/
	
	//开启ADC_DMA搬运功能
	ADC_DMACmd(ADC1,ENABLE);
	
	//ADC使能
	ADC_Cmd(ADC1,ENABLE);

	ADC_ResetCalibration(ADC1);
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	
	//已开启ADC连续转换、DMA循环转移指令
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//用于启动 ADC(模数转换器)的软件转换命令
}



#ifndef _AD_H
#define _AD_H

extern uint16_t AD_Value[4];
//ADC初始化
void AD_Init(void);

#endif


===============================


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"


int  main(void)
{
	OLED_Init();
//初始化配置ADC通道(几个菜)、连续转换、不偏移、右对齐、中速
//DMA初始化配置循环转移、偏移、字节对字节、源地址目标地址、软件还是硬件触发	
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
		OLED_ShowNum(1,5,AD_Value[0],4);
		OLED_ShowNum(2,5,AD_Value[1],4);
		OLED_ShowNum(3,5,AD_Value[2],4);
		OLED_ShowNum(4,5,AD_Value[3],4);

		Delay_ms(100);
	}
}



UART串口

串口硬件和软件通识 

 

电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

TTL电平:+3.3V或+5V表示1,0V表示0

RS232电平:-3~-15V表示1,+3~+15V表示0

RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号) 

 

 

 

 

STM32的八种GPIO模式包括:

  1. 模拟输入模式(GPIO_Mode_AIN):关闭施密特触发器,将电压信号传送到片上外设模块,不接上下拉电阻。
  2. 浮空输入模式(GPIO_Mode_IN_FLOATING):没有接上下拉电阻的输入状态。
  3. 下拉输入模式(GPIO_Mode_IPD):有接下拉电阻的输入状态。
  4. 上拉输入模式(GPIO_Mode_IPU):有接上拉电阻的输入状态。
  5. 开漏输出模式(GPIO_Mode_Out_OD):输出状态由外部电路控制,无法真正输出高电平或低电平。
  6. 推挽输出模式(GPIO_Mode_Out_PP):可以真正输出高电平和低电平,IO口的输出状态由输出寄存器决定。
  7. 复用开漏输出模式:输出状态由外部电路控制,无法真正输出高电平或低电平,但可以用作第二功能。
  8. 复用推挽输出模式:可以真正输出高电平和低电平,且可以用作第二功能。

 提取数字小技巧

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y)//12345 5
{
    uint32_t Resalt = 1;
    while(Y--)
    {
        Resalt *= X;
    }
    return Resalt;
}
/*
通过循环和除法运算,将数字Number转换为字符串形式。具体来说,
每次循环都会取出数字的某一位,然后将其转换为字符并发送出去。
在最后一行代码中,+ '0'的作用是将数字转换为字符。
这是因为在ASCII码表中,字符'0'到'9'的编码是连续的,所以通过加上'0',可以将数字转换为对应的字符
*/

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght)
{
    uint16_t i;
    for(i = 0; i < Lenght; i++)
    {
        //12345 / 10^4 = 1%10 = 1         +0
        //12345 / 10^3 = 12%10 = 2
        //12345 / 10^2 = 123%10 = 3
        Serial_SendByte(Number / Serial_Pow(10,Lenght - 1 -i) % 10 +'0');
    }
}

 解决串口输出中文乱码问题

 代码段 ---- 串口发送

/*
Serial.c
*/

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

//串口初始化 对应引脚 串口号
void Serial_Init(void)
{
	//gpio 和 uart时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//gpio初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	   //PA9 对应usart1 TX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//uart初始化
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//否使用硬件流控发送和接收。 不使用
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//模式位发送
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位 不需要检验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位 一位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位长度为 8bit
	USART_Init(USART1,&USART_InitStructure);
	
	//开启uart能使
	USART_Cmd(USART1,ENABLE);
}

//串口发送数据 一个byte  8个位
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
	//USART_GetFlagStatus发送后获取标志位状态 USART_FLAG_TXE 为发送寄存器为空标志  注意别用到中断函数了
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//0:数据还没有被转移到移位寄存器 则一直循环 1跳出
	USART_ClearFlag(USART1,USART_IT_TXE);
}



//串口发送数组 传入数组首地址、和数组长度
void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String)
{
	uint16_t i;
	for(i = 0; String[i] != '\0'; i++)
	{
		Serial_SendByte(String[i]);
	}
}

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y)//12345 5
{
	uint32_t Resalt = 1;
	while(Y--)
	{
		Resalt *= X;
	}
	return Resalt;
}
/*
通过循环和除法运算,将数字Number转换为字符串形式。具体来说,
每次循环都会取出数字的某一位,然后将其转换为字符并发送出去。
在最后一行代码中,+ '0'的作用是将数字转换为字符。
这是因为在ASCII码表中,字符'0'到'9'的编码是连续的,所以通过加上'0',可以将数字转换为对应的字符
*/

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght)
{
	uint16_t i;
	for(i = 0; i < Lenght; i++)
	{
		//12345 / 10^4 = 1%10 = 1 		+0
		//12345 / 10^3 = 12%10 = 2
		//12345 / 10^2 = 123%10 = 3
		Serial_SendByte(Number / Serial_Pow(10,Lenght - 1 -i) % 10 +'0');
	}
}


/*
要勾选 LIB库 并加头文件 <stdio.h>
这个函数的作用是将字符ch写入到文件流f中。它接受两个参数:
int ch:要写入的字符。
FILE* f:目标文件流,即要将字符写入的文件。
*/
//重写printf函数
int fputc(int ch,FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}


//封装sprintf函数
void Serial_Printf(char* format, ...)//format是类型 arg是 ...参数列表中的参数
{
	/// 定义一个字符数组 String,用于存储格式化后的字符串
	char String[100];
	
    // 定义一个可变参数列表 arg,用于接收不定数量的参数
    va_list arg;

    // 使用 va_start 宏初始化 arg,使其指向第一个可变参数
    va_start(arg, format);

    // 使用 vsprintf 函数将格式化后的字符串写入 String 中
    vsprintf(String, format, arg);

    // 使用 va_end 宏结束可变参数列表的使用
    va_end(arg);

    // 调用 Serial_SendString 函数将格式化后的字符串通过串口发送出去
    Serial_SendString(String);
	
}

/*
serial.h  main
*/

#ifndef _SERIAL_H
#define _SERIAL_H
#include <stdio.h>
#include <stdarg.h>

//串口初始化配置
void Serial_Init(void);

//串口发送1个字节
void Serial_SendByte(uint8_t Byte);

//串口发送数组 
void Serial_SendArray(uint8_t* Array,uint16_t Length);

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String);

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y);

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght);

//重写printf函数 fputc是printf的底层
int fputc(int ch,FILE* f);


//封装sprintf函数
void Serial_Printf(char* format, ...);

#endif



========================

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"



int  main(void)
{
	OLED_Init();
	Serial_Init();
	
	Serial_SendByte('B');
	
	uint8_t Array[] = {0x41,0x42,0x43,0x44};
	Serial_SendArray(Array,4);
	

	Serial_SendString("\r\nhello world!");
	
	Serial_SendNumber(12345,5);

	printf("Num = %d\r\n",666); //定向到串口1 串口2无法使用
	
	char String[100];
	sprintf(String,"Num = %d\r\n",666);//多个串口可用 指定打印再String数组中
	Serial_SendString(String);
	
	
	Serial_Printf("Num = %d\r\n",777);//对sprintf的封装
	Serial_Printf("你好 . 世界\r\n");//对sprintf的封装
	while(1)
	{

	}
}


代码段 ---- 串口接收单个数据

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

//串口初始化 对应引脚 串口号
void Serial_Init(void)
{
	//gpio 和 uart时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//gpio初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	   //PA9 对应usart1 TX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	   //PA10 对应usart1 RX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//uart初始化
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//否使用硬件流控发送和接收。 不使用
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//模式位发送
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位 不需要检验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位 一位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位长度为 8bit
	USART_Init(USART1,&USART_InitStructure);
	
	//开启uart能使
	USART_Cmd(USART1,ENABLE);
}

//串口发送数据 一个byte  8个位
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
	//USART_GetFlagStatus发送后获取标志位状态 USART_FLAG_TXE 为发送寄存器为空标志  注意别用到中断函数了
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//0:数据还没有被转移到移位寄存器 则一直循环 1跳出
	USART_ClearFlag(USART1,USART_IT_TXE);
}



//串口发送数组 传入数组首地址、和数组长度
void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String)
{
	uint16_t i;
	for(i = 0; String[i] != '\0'; i++)
	{
		Serial_SendByte(String[i]);
	}
}

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y)//12345 5
{
	uint32_t Resalt = 1;
	while(Y--)
	{
		Resalt *= X;
	}
	return Resalt;
}
/*
通过循环和除法运算,将数字Number转换为字符串形式。具体来说,
每次循环都会取出数字的某一位,然后将其转换为字符并发送出去。
在最后一行代码中,+ '0'的作用是将数字转换为字符。
这是因为在ASCII码表中,字符'0'到'9'的编码是连续的,所以通过加上'0',可以将数字转换为对应的字符
*/

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght)
{
	uint16_t i;
	for(i = 0; i < Lenght; i++)
	{
		//12345 / 10^4 = 1%10 = 1 		+0
		//12345 / 10^3 = 12%10 = 2
		//12345 / 10^2 = 123%10 = 3
		Serial_SendByte(Number / Serial_Pow(10,Lenght - 1 -i) % 10 +'0');
	}
}


/*
要勾选 LIB库 并加头文件 <stdio.h>
这个函数的作用是将字符ch写入到文件流f中。它接受两个参数:
int ch:要写入的字符。
FILE* f:目标文件流,即要将字符写入的文件。
*/
//重写printf函数
int fputc(int ch,FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}


//封装sprintf函数
void Serial_Printf(char* format, ...)//format是类型 arg是 ...参数列表中的参数
{
	/// 定义一个字符数组 String,用于存储格式化后的字符串
	char String[100];
	
    // 定义一个可变参数列表 arg,用于接收不定数量的参数
    va_list arg;

    // 使用 va_start 宏初始化 arg,使其指向第一个可变参数
    va_start(arg, format);

    // 使用 vsprintf 函数将格式化后的字符串写入 String 中
    vsprintf(String, format, arg);

    // 使用 va_end 宏结束可变参数列表的使用
    va_end(arg);

    // 调用 Serial_SendString 函数将格式化后的字符串通过串口发送出去
    Serial_SendString(String);
	
}

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int  main(void)
{
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"USART:");

	while(1)
	{
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == 1)
		{
			RxData = USART_ReceiveData(USART1);//读取完数据 自动清楚标志位
			OLED_ShowHexNum(1,8,RxData,2);

		}
	}
}


代码段 ---- 串口接收中断 实现收和发

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxFlag;//存放中断状态
uint8_t Serial_RxData;//存放接收数据

//串口初始化 对应引脚 串口号
void Serial_Init(void)
{
	//gpio 和 uart时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//gpio初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	   //PA9 对应usart1 TX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	   //PA10 对应usart1 RX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//uart初始化
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//否使用硬件流控发送和接收。 不使用
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//模式位发送
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位 不需要检验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位 一位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位长度为 8bit
	USART_Init(USART1,&USART_InitStructure);
	
	//串口中断配置	接收中断 检测到串口接收寄存器不为空时产生中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	
	//中断分组 分组为2  0-3 和 0-3
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	
	//NVIC初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//NVIC中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	
	//开启uart能使
	USART_Cmd(USART1,ENABLE);
}

//串口发送数据 一个byte  8个位
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
	//USART_GetFlagStatus发送后获取标志位状态 USART_FLAG_TXE 为发送寄存器为空标志  注意别用到中断函数了
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//0:数据还没有被转移到移位寄存器 则一直循环 1跳出
}



//串口发送数组 传入数组首地址、和数组长度
void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String)
{
	uint16_t i;
	for(i = 0; String[i] != '\0'; i++)
	{
		Serial_SendByte(String[i]);
	}
}

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y)//12345 5
{
	uint32_t Resalt = 1;
	while(Y--)
	{
		Resalt *= X;
	}
	return Resalt;
}
/*
通过循环和除法运算,将数字Number转换为字符串形式。具体来说,
每次循环都会取出数字的某一位,然后将其转换为字符并发送出去。
在最后一行代码中,+ '0'的作用是将数字转换为字符。
这是因为在ASCII码表中,字符'0'到'9'的编码是连续的,所以通过加上'0',可以将数字转换为对应的字符
*/

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght)
{
	uint8_t i;
	for(i = 0; i < Lenght; i++)
	{
		//12345 / 10^4 = 1%10 = 1 		+0
		//12345 / 10^3 = 12%10 = 2
		//12345 / 10^2 = 123%10 = 3
		Serial_SendByte(Number / Serial_Pow(10,Lenght - 1 -i) % 10 + '0');//8 位减8位
	}
}


/*
要勾选 LIB库 并加头文件 <stdio.h>
这个函数的作用是将字符ch写入到文件流f中。它接受两个参数:
int ch:要写入的字符。
FILE* f:目标文件流,即要将字符写入的文件。
*/
//重写printf函数
int fputc(int ch,FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}


//封装sprintf函数
void Serial_Printf(char* format, ...)//format是类型 arg是 ...参数列表中的参数
{
	/// 定义一个字符数组 String,用于存储格式化后的字符串
	char String[100];
	
    // 定义一个可变参数列表 arg,用于接收不定数量的参数
    va_list arg;

    // 使用 va_start 宏初始化 arg,使其指向第一个可变参数
    va_start(arg, format);

    // 使用 vsprintf 函数将格式化后的字符串写入 String 中
    vsprintf(String, format, arg);

    // 使用 va_end 宏结束可变参数列表的使用
    va_end(arg);

    // 调用 Serial_SendString 函数将格式化后的字符串通过串口发送出去
    Serial_SendString(String);
}

//保存接收中断状态
uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;//要带返回值
	}
	return 0;
}


//保存收到的数据并返回
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}


//串口中断函数 接收中断
void USART1_IRQHandler(void)
{
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)//获取串口中断状态 为1 则中断
	{
		Serial_RxData = USART_ReceiveData(USART1);//读操作可以将该位清0 
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//没读取也手动清除一下标志位
	}
	
}

#ifndef _SERIAL_H
#define _SERIAL_H
#include <stdio.h>
#include <stdarg.h>

//串口初始化配置
void Serial_Init(void);

//串口发送1个字节
void Serial_SendByte(uint8_t Byte);

//串口发送数组 
void Serial_SendArray(uint8_t* Array,uint16_t Length);

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String);

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y);

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght);

//重写printf函数 fputc是printf的底层
int fputc(int ch,FILE* f);

//封装sprintf函数
void Serial_Printf(char* format, ...);

//保存接收中断状态
uint8_t Serial_GetRxFlag(void);

//保存收到的数据并返回
uint8_t Serial_GetRxData(void);


#endif



=============================


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;
int  main(void)
{
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"USART:");
	while(1)
	{
		if(Serial_GetRxFlag() == 1)//接收到数据 标志位为1 后变为0
		{
			RxData = Serial_GetRxData();//通过Serial_GetRxData返回值存放到RxData中
			Serial_SendByte(RxData);	//再通过串口发送到上位机显示
			OLED_ShowHexNum(1,8,RxData,2);//OLED显示RxData值
		}
	}
}

代码段 ---- 串口收发数据包并OLED显示 

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxFlag;//存放中断状态
uint8_t Serial_TxPacket[4];//存放发送数据包
uint8_t Serial_RxPacket[4];//存放接收数据包

//串口初始化 对应引脚 串口号
void Serial_Init(void)
{
	//gpio 和 uart时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//gpio初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	   //PA9 对应usart1 TX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	   //PA10 对应usart1 RX
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//uart初始化
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//否使用硬件流控发送和接收。 不使用
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//模式位发送
	USART_InitStructure.USART_Parity = USART_Parity_No;//检验位 不需要检验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位 一位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位长度为 8bit
	USART_Init(USART1,&USART_InitStructure);
	
	//串口中断配置	接收中断 检测到串口接收寄存器不为空时产生中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	
	//中断分组 分组为2  0-3 和 0-3
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	
	//NVIC初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//NVIC中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	
	//开启uart能使
	USART_Cmd(USART1,ENABLE);
}

//串口发送数据 一个byte  8个位
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1,Byte);
	//USART_GetFlagStatus发送后获取标志位状态 USART_FLAG_TXE 为发送寄存器为空标志  注意别用到中断函数了
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//0:数据还没有被转移到移位寄存器 则一直循环 1跳出
}



//串口发送数组 传入数组首地址、和数组长度
void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String)
{
	uint16_t i;
	for(i = 0; String[i] != '\0'; i++)
	{
		Serial_SendByte(String[i]);
	}
}

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y)//12345 5
{
	uint32_t Resalt = 1;
	while(Y--)
	{
		Resalt *= X;
	}
	return Resalt;
}
/*
通过循环和除法运算,将数字Number转换为字符串形式。具体来说,
每次循环都会取出数字的某一位,然后将其转换为字符并发送出去。
在最后一行代码中,+ '0'的作用是将数字转换为字符。
这是因为在ASCII码表中,字符'0'到'9'的编码是连续的,所以通过加上'0',可以将数字转换为对应的字符
*/

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght)
{
	uint8_t i;
	for(i = 0; i < Lenght; i++)
	{
		//12345 / 10^4 = 1%10 = 1 		+0
		//12345 / 10^3 = 12%10 = 2
		//12345 / 10^2 = 123%10 = 3
		Serial_SendByte(Number / Serial_Pow(10,Lenght - 1 -i) % 10 + '0');//8 位减8位
	}
}


/*
要勾选 LIB库 并加头文件 <stdio.h>
这个函数的作用是将字符ch写入到文件流f中。它接受两个参数:
int ch:要写入的字符。
FILE* f:目标文件流,即要将字符写入的文件。
*/
//重写printf函数
int fputc(int ch,FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}


//封装sprintf函数
void Serial_Printf(char* format, ...)//format是类型 arg是 ...参数列表中的参数
{
	/// 定义一个字符数组 String,用于存储格式化后的字符串
	char String[100];
	
    // 定义一个可变参数列表 arg,用于接收不定数量的参数
    va_list arg;

    // 使用 va_start 宏初始化 arg,使其指向第一个可变参数
    va_start(arg, format);

    // 使用 vsprintf 函数将格式化后的字符串写入 String 中
    vsprintf(String, format, arg);

    // 使用 va_end 宏结束可变参数列表的使用
    va_end(arg);

    // 调用 Serial_SendString 函数将格式化后的字符串通过串口发送出去
    Serial_SendString(String);
}

//保存接收中断状态
uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;//要带返回值
	}
	return 0;
}

//发送TX数据包  
void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket,4);
	Serial_SendByte(0xFE);
}

//串口中断函数 接收中断 读取数据
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;//接收数据包 标志位
	static uint8_t PRxPacket = 0;//表示数据包中的位置 指示接收到哪一个了
	
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)//获取串口中断状态 为1 则中断
	{
		
		uint8_t RxData = USART_ReceiveData(USART1);//读取数据存放到RxData中
		
		if (RxData == 0xFF)		//第一位收到是FF 则数据包标志位为1 且接收标志位状态为0
		{
			RxState = 1;//数据包标志位
			Serial_RxFlag = 0;//接收标志位
		}
		else if(RxState == 1)//数据包标志位为1 接收到的数据偏移存放,
		{
			Serial_RxPacket[PRxPacket] = RxData;
			PRxPacket ++;
			if(PRxPacket == 4)//当PRxPacket == 4时复位改变数据包标志位为2
			{				
				RxState = 2;
				PRxPacket = 0;
			}
		}
		else if(RxState == 2)//数据包标志位为2 则又将数据包标志位为0 接收标志位为1
		{
			RxState = 0;
			Serial_RxFlag = 1;
		}

		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//没读取也手动清除一下标志位
	}
	
}

#ifndef _SERIAL_H
#define _SERIAL_H
#include <stdio.h>
#include <stdarg.h>

extern uint8_t Serial_TxPacket[];//存放发送数据包
extern uint8_t Serial_RxPacket[];//存放接收数据包

//串口初始化配置
void Serial_Init(void);

//串口发送1个字节
void Serial_SendByte(uint8_t Byte);

//串口发送数组 
void Serial_SendArray(uint8_t* Array,uint16_t Length);

//串口发送字符串 出入首地址 直至偏移到'\0'
void Serial_SendString(char* String);

//与下面函数配合 用/x次方去掉右边不想要数据 返回多少次方
uint32_t Serial_Pow(uint32_t X,uint8_t Y);

//发送数字 传过来一串数字X 位数Y
void Serial_SendNumber(uint32_t Number,uint8_t Lenght);

//重写printf函数 fputc是printf的底层
int fputc(int ch,FILE* f);

//封装sprintf函数
void Serial_Printf(char* format, ...);

//保存接收中断状态
uint8_t Serial_GetRxFlag(void);

//发送TX数据包  
void Serial_SendPacket(void);
#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t RxData;
int  main(void)
{
	uint8_t KeyNum;
	
	OLED_Init();
	Serial_Init();
	Key_Init();
	
	OLED_ShowString(1,1,"TxPacket:");
	OLED_ShowString(3,1,"RxPacket:");
	
	Serial_TxPacket[0] = 0x00;
	Serial_TxPacket[1] = 0x01;
	Serial_TxPacket[2] = 0x02;
	Serial_TxPacket[3] = 0x03;

	OLED_ShowHexNum(2,1,Serial_TxPacket[0],2);
	OLED_ShowHexNum(2,4,Serial_TxPacket[1],2);
	OLED_ShowHexNum(2,7,Serial_TxPacket[2],2);
	OLED_ShowHexNum(2,10,Serial_TxPacket[3],2);		
	
	while(1)
	{
		KeyNum = Key_getNum();
		if(KeyNum == 1)
		{
			Serial_TxPacket[0]++;
			Serial_TxPacket[1]++;
			Serial_TxPacket[2]++;
			Serial_TxPacket[3]++;
			
			Serial_SendPacket();
			
			OLED_ShowHexNum(2,1,Serial_TxPacket[0],2);
			OLED_ShowHexNum(2,4,Serial_TxPacket[1],2);
			OLED_ShowHexNum(2,7,Serial_TxPacket[2],2);
			OLED_ShowHexNum(2,10,Serial_TxPacket[3],2);	
		}
		
		if(Serial_GetRxFlag() == 1)
		{
			OLED_ShowHexNum(4,1,Serial_RxPacket[0],2);
			OLED_ShowHexNum(4,4,Serial_RxPacket[1],2);
			OLED_ShowHexNum(4,7,Serial_RxPacket[2],2);
			OLED_ShowHexNum(4,10,Serial_RxPacket[3],2);			
		}
	}
}


I2C传输

基础通识

I2C协议的工作原理可以概括为以下几个步骤:

  1. 起始信号:由主机发出,它是一个高电平信号。这个信号会通知所有被寻址的器件,它们都被看作是从机。

  2. 地址传输:主机接着发送一个包含7位地址和一位读写控制位的数据包。每个连接到总线的设备都有唯一的地址,接收到这个数据包的设备会根据地址判断是否是自己,如果是,它会作出响应。

  3. 数据传输:如果设备决定响应主机的请求(无论是读还是写),它会继续发送一个应答信号,然后开始数据传输。对于写操作,主机将数据发送到从机;对于读操作,从机将数据发送到主机。

  4. 确认信号:每当一个字节的数据被传输完成,接收方会产生一个应答信号来通知发送方数据已经被正确接收。发送方在收到确认信号后才能停止发送数据。

  5. 终止信号:数据传输结束后,主机会发出一个终止信号,这是一个低电平信号。所有的通信过程都会以此信号作为结束标志。

 

 

 

MPU-6050介绍 

 

 

 代码段 ---- I2C软件模拟

#include "stm32f10x.h"                  // Device header
#include "Delay.h"  

//i2c引脚初始化
void MyI2C_Init(void)
{
	//配置寄存器B时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//结构体名字 及配置
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//I2C开漏输出 也可以输入
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10|GPIO_Pin_11;//PB10接SCL PB11接SDA
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	
	//配置完初始化  
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//把该引脚拉高 初始化状态
	GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}

//该函数通过传入0 1参数可写PB10引脚状态
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);//防止频率过快 mpu速度跟不上
}

//该函数通过传入0 1参数可写PB11引脚状态
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);//防止频率过快 mpu速度跟不上
}

//读SDA
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);//读取SDA PB11引脚
	Delay_us(10);//防止频率过快 mpu速度跟不上
	return BitValue;
}

//START开始  SDA SCL都拉高 后拉低
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);//先拉高SDA 因为如果SCL为高的话SDA不可变
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

//STOP结束 根据时序图 SDA不确定 所以先拉低SDA 释放SCL 释放SDA 
void MyI2C_Stop(void)
{	
	MyI2C_W_SDA(0);	
	MyI2C_W_SCL(1);	
	MyI2C_W_SDA(1);	
}

//发送数据	 1.主机数据放到SDA上 串行高到低放入 拉低SCL	2.SCL释放从机读走数据 3.SCL拉低主机再次填入数据到SDA
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for(i = 0; i < 8; i++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));//把主机数据中 高位一位一位放到SDA中 分8次放入
		MyI2C_W_SCL(1);//释放 读取从机读走SDA中数据
		MyI2C_W_SCL(0);//再次拉低 等待主机数据放入SDA中		
	}
}

//接收数据 按8个位传输
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t Byte = 0x00;//用于存放接收数据
	uint8_t i;
	MyI2C_W_SDA(1);//释放了SDA(数据线)的控制权给主机。这是为了确保从机可以开始发送数据
	
	for(i = 0; i < 8; i++)
	{
		MyI2C_W_SCL(1);//释放了SCL(时钟线)的控制权给主机,使主机能够读取数据
		if(MyI2C_R_SDA() == 1){//读取从机的数据线上的信号。如果读取到的信号为1,则将相应的位设置为1
			Byte |= (0x80 >> i);
		}
		MyI2C_W_SCL(0);//再次拉低 从机把数据放到SDA上
	}
	return Byte;
}

//发送ACK应答 一个字节
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);//主机把数据放到SDA上
	MyI2C_W_SCL(1);		//释放SCL 读取从机读走SDA中数据
	MyI2C_W_SCL(0);		//拉低SCL 等待主机数据放入SDA中
}

//获取ACK应答 一个字节
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;//用于存放接收数据
	MyI2C_W_SDA(1);//释放了SDA(数据线)的控制权给主机。这是为了确保从机可以开始发送数据
	MyI2C_W_SCL(1);//释放了SCL(时钟线)的控制权给主机,使主机能够读取数据
	AckBit = MyI2C_R_SDA();//若从机发送完数据到SDA 则 SDA会拉低
	MyI2C_W_SCL(0);//再次拉低 从机把数据放到SDA上
	return AckBit;
}

#ifndef _MYI2C_H
#define _MYI2C_H

//i2c引脚初始化
void MyI2C_Init(void);

//该函数通过传入0 1参数可写PB10引脚状态
void MyI2C_W_SCL(uint8_t BitValue);

//该函数通过传入0 1参数可写PB11引脚状态
void MyI2C_W_SDA(uint8_t BitValue);

//读SDA
uint8_t MyI2C_R_SDA(void);

//START开始  SDA SCL都拉高 后拉低
void MyI2C_Start(void);

//STOP结束 根据时序图 SDA不确定 所以先拉低SDA 释放SCL 释放SDA 
void MyI2C_Stop(void);

//发送数据	 1.主机数据放到SDA上 串行高到低放入 拉低SCL	2.SCL释放从机读走数据 3.SCL拉低主机再次填入数据到SDA
void MyI2C_SendByte(uint8_t Byte);

//接收数据 按8个位传输
uint8_t MyI2C_ReceiveByte(void);

//发送ACK应答 一个字节
void MyI2C_SendAck(uint8_t AckBit);

//获取ACK应答 一个字节
uint8_t MyI2C_ReceiveAck(void);

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyI2c.h"

int  main(void)
{
	uint8_t Ack;
	OLED_Init();
	MyI2C_Init();
	MyI2C_Start();
	MyI2C_SendByte(0xD0);
	Ack = MyI2C_ReceiveAck();
	MyI2C_Stop();
	
	OLED_ShowNum(1,1,Ack,3);
	while(1)
	{

	}
}

代码段 ---- MPU6050 通过I2C传输

//mpu6050.c

#include "stm32f10x.h"                  // Device header
#include "MyI2c.h"
#include "MPU6050_Reg.h"

//宏硬件地址
#define MPU6050_ADDRESS		0xD0    //硬件地址

//MPU发送数据  主机发送挂数据到SDA上
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
	MyI2C_Start();					//开始
	MyI2C_SendByte(MPU6050_ADDRESS);//硬件地址
	MyI2C_ReceiveAck();				//返回应答状态 0应答1无应答
	MyI2C_SendByte(RegAddress);		//寄存器地址
	MyI2C_ReceiveAck();				//返回应答状态
	MyI2C_SendByte(Data);			//发送数据
	MyI2C_ReceiveAck();				//返回应答状态
	MyI2C_Stop();					//停止
}

//MPC接收数据1次 要改变
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();					//开始
	MyI2C_SendByte(MPU6050_ADDRESS);//硬件地址
	MyI2C_ReceiveAck();				//返回应答状态
	MyI2C_SendByte(RegAddress);		//寄存器地址
	MyI2C_ReceiveAck();				//返回应答状态
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//该寄存器为接收模式
	MyI2C_ReceiveAck();				//返回应答状态
	Data = MyI2C_ReceiveByte();		//主机接收SDA
	MyI2C_SendAck(1);				//从机发送应答状态为1 只发送一次
	MyI2C_Stop();
	
	return Data;
}

//MPU初始化 并配置其它寄存器
void MPU6050_Init(void)
{
	MyI2C_Init();
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //电源管理寄存器1地址 陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); //电源管理寄存器2地址 6轴不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);  //几分频 越小越好
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);		//配置寄存器 0000不同步 0110滤波器 
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪配置寄存器
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度配置寄存器
}

//MPU获取xyz数据 通过指针 地址保存数据
void MPU6050_GetData(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
					int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ)
{
	uint16_t DataH,DataL;
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

//MPU.H MPU_Reg.h

#ifndef _MPU6050_H
#define _MPU6050_H

//MPU发送数据  主机发送挂数据到SDA上
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);

//MPC接收数据1次 要改变
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

//MPU初始化 并配置其它寄存器
void MPU6050_Init(void);

//MPU获取xyz数据
void MPU6050_GetData(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
					int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ);
#endif





#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif
//main

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;

int  main(void)
{
	uint8_t ID;
	
	OLED_Init();
	MPU6050_Init();//MPU6050初始化 初始化I2C 和MPU的各种模式和寄存器
	
	MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);//传递地址过去 对面用指针存放数据保存起来
	
	ID = MPU6050_ReadReg(0x75);//发送寄存器的地址 返回ID值
	OLED_ShowString(1,1,"ID:");
	OLED_ShowHexNum(1,5,ID,2);
	
	while(1)
	{
		MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		OLED_ShowSignedNum(2,8,GX,5);
		OLED_ShowSignedNum(3,8,GY,5);
		OLED_ShowSignedNum(4,8,GZ,5);
	}
}

 I2C硬件资源

硬件I2C

I2C_InitTypeDef这个函数主要用于对I2C进行初始化配置,它包含以下参数:

  1. uint32_t I2C_ClockSpeed:设置SCL时钟频率,此值需不低于40000。另外,有的资料指出其频率不得高于400kHz,这可能与具体硬件和库有关,需要根据实际情况设置。

  2. uint16_t I2C_Mode:用于指定工作模式,可以选择I2C模式或SMBus主从模式。

  3. uint16_t I2C_DutyCycle:用于指定时钟占空比,可以选择低/高=2:1以及16:9模式。

  4. uint16_t I2C_OwnAddress1:表示I2C设备自身的地址。

  5. FunctionalState I2C_Ack:应答使能,若使能后可以发送响应信号。

  6. uint16_t I2C_AcknowledgedAddress:用于设置地址长度,可以是7位或10位。

硬件I2C ---- 代码段

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

//宏硬件地址
#define MPU6050_ADDRESS		0xD0

//该函数防止程序while卡死  且更易于修改
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint16_t Timeout ;
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
	{
		Timeout++;
		if(Timeout == 10000){
			break;
		}
	}
}


//MPU发送数据  主机发送挂数据到SDA上
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
	
//软件模拟
//	MyI2C_Start();					//开始
//	MyI2C_SendByte(MPU6050_ADDRESS);//硬件地址
//	MyI2C_ReceiveAck();				//返回应答状态 0应答1无应答
//	MyI2C_SendByte(RegAddress);		//寄存器地址
//	MyI2C_ReceiveAck();				//返回应答状态
//	MyI2C_SendByte(Data);			//发送数据
//	MyI2C_ReceiveAck();				//返回应答状态
//	MyI2C_Stop();					//停止
	
	I2C_GenerateSTART(I2C2,ENABLE);//I2C开始信号 Generate产生
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//如果没有发送EV5事件,就循环等待 发送跳出循环 表示在 I2C 通信中选择主模式
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送7位硬件地址 发送模式 内置ACK不用配置
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//如果没有发送EV6事件,就循环等待 表示在 I2C 通信中选择主发送器模式
	
	I2C_SendData(I2C2,RegAddress);//发送数据 写入移位寄存器 --》 数据寄存器
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8是一个事件,表示在 I2C 通信中正在发送字节数据
	
	I2C_SendData(I2C2,Data);												//发送数据
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//表示在 I2C 通信中已经成功发送了一个字节的数据。

	I2C_GenerateSTOP(I2C2,ENABLE);									//I2C停止

}




//MPC接收数据1次 要改变
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
//	MyI2C_Start();					//开始
//	MyI2C_SendByte(MPU6050_ADDRESS);//硬件地址
//	MyI2C_ReceiveAck();				//返回应答状态
//	MyI2C_SendByte(RegAddress);		//寄存器地址
//	MyI2C_ReceiveAck();				//返回应答状态
//	
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//该寄存器为接收模式
//	MyI2C_ReceiveAck();				//返回应答状态
//	Data = MyI2C_ReceiveByte();		//主机接收SDA
//	MyI2C_SendAck(1);				//从机发送应答状态为1 只发送一次
//	MyI2C_Stop();
	
	//前面几个一样 后面配置成读模式
	I2C_GenerateSTART(I2C2,ENABLE);//I2C开始信号 Generate产生
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5如果没有发送事件,就循环等待 发送跳出循环 表示在 I2C 通信中选择主模式
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送7位硬件地址 发送模式 内置ACK不用配置
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6如果没有发送事件,就循环等待 表示在 I2C 通信中选择主发送器模式
	
	I2C_SendData(I2C2,RegAddress);//发送7位寄存器地址 ;//发送7位寄存器地址 发送模式
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8是一个事件,表示在 I2C 通信中正在发送字节数据
	
	
	
	I2C_GenerateSTART(I2C2,ENABLE);//发送寄存器地址数据 写入移位寄存器 --》 数据寄存器
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5 选择主模式
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);//发送硬件地址 配置成接收模式
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6配置成接收模式
	
	I2C_AcknowledgeConfig(I2C2,DISABLE);//关闭应答 只接收一次
	I2C_GenerateSTOP(I2C2,ENABLE);//关闭I2C
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//是一个事件,表示在 I2C 通信中已经成功接收了一个字节的数据。
	Data = I2C_ReceiveData(I2C2);				//接收到数据
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);//恢复默认配置
	
	return Data;

}

//MPU初始化 并配置其它寄存器
void MPU6050_Init(void)
{
//	MyI2C_Init();
	
	//开启I2C GPIO时钟 SDA接I2C2的PB11 SCL接PB10
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;//SDA接I2C2的PB11 SCL接PB10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//配置成复用开漏输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//I2C2初始化
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//模式为i2c
	I2C_InitStructure.I2C_ClockSpeed = 50000; //置SCL时钟频率,此值需不低于40000
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//应答使能,若使能后可以发送响应信号。
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//用于指定时钟占空比,可以选择低/高=2:1以及16:9模式
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//用于设置地址长度,可以是7位或10位	
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;//表示I2C设备自身的地址 不能能从机地址一样
	I2C_Init(I2C2,&I2C_InitStructure);
	
	//开启I2C
	I2C_Cmd(I2C2,ENABLE);
	
	
	
	//MPU6050初始化寄存器配置
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //电源管理寄存器1地址 陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); //电源管理寄存器2地址 6轴不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);  //几分频 越小越好
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);		//配置寄存器 0000不同步 0110滤波器 
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪配置寄存器
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度配置寄存器
}

//MPU获取xyz数据 通过指针 地址保存数据
void MPU6050_GetData(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
					int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ)
{
	uint16_t DataH,DataL;
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//通过I2C发送MPU某个地址 读取x加速度返回值
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);//通过I2C发送MPU某个地址 读取x角速度返回值
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

#ifndef _MPU6050_H
#define _MPU6050_H

//MPU发送数据  主机发送挂数据到SDA上
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);

//MPC接收数据1次 要改变
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

//MPU初始化 并配置其它寄存器
void MPU6050_Init(void);

//MPU获取xyz数据
void MPU6050_GetData(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
					int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ);

//该函数防止程序while卡死  且更易于修改
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
#endif

======================

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;

int  main(void)
{
	uint8_t ID;
	
	OLED_Init();
	MPU6050_Init();//MPU6050初始化 初始化I2C 和MPU的各种模式和寄存器
	
	MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);//传递地址过去 对面用指针存放数据保存起来
	
	ID = MPU6050_ReadReg(0x75);//发送寄存器的地址 返回ID值
	OLED_ShowString(1,1,"ID:");
	OLED_ShowHexNum(1,5,ID,2);
	
	while(1)
	{
		MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		OLED_ShowSignedNum(2,8,GX,5);
		OLED_ShowSignedNum(3,8,GY,5);
		OLED_ShowSignedNum(4,8,GZ,5);
	}
}

环境和调试

51和32不同环境(需要对应芯片包)

 

 keil调试

参考博文【工具使用】Keil常用的调试操作整理介绍_keil调试-CSDN博客

Debug详细使用  keil在Debug的高级用法

仿真时51不能实时更新数据,32可以实时更新,需要勾选

kile中无法提示补全信息如何设置 请看

 

可修改复位电压

 

VSCode环境配置

 1.软件安装

2.c编译器安装 ----- 更新后网页无法下载 得从git上下载

添加搭配环境

 

vscode安装后装相应插件

 

添加setting.josn

 {
  "breadcrumbs.enabled": true,
  // vscode默认启用了根据文件类型自动设置tabsize的选项
  "editor.detectIndentation": true,
  "editor.insertSpaces": true,
  // 重新设定tabsize
  // "editor.tabSize": 2,
  // 显示 markdown 中英文切换时产生的特殊字符
  "editor.renderControlCharacters": true,
  //编辑器默认的代码检查规则关闭
  "javascript.format.enable": false,
  //eslint检查是否启动
  "eslint.enable": true,
  "explorer.confirmDelete": true,
 
  // #每次保存的时候自动格式化
  "editor.formatOnSave": false,
  "editor.formatOnType": false, // 在键入一行后是否自动化格式
  // "editor.wordWrap": "wordWrapColumn", // 换行规则,off 永不换行
  // 120 列后换行
  // "editor.wordWrapColumn": 120,
  "editor.codeActionsOnSave": {
    // "source.fixAll.stylelint": true, //是否选择启用stylelint
    // "source.fixAll.eslint": true
    "source.fixAll": true
  },
  "editor.quickSuggestions": {
    "strings": true
  },
  // #每次保存的时候将代码按eslint格式进行修复
  "eslint.autoFixOnSave": true,
  "eslint.options": {
    "extensions": [".js", ".vue"]
    // "plugins": ["html"]
  },
  //
  "files.autoSave": "off",
  "editor.formatOnPaste": true,
  "editor.multiCursorModifier": "ctrlCmd",
  "editor.snippetSuggestions": "top",
  "editor.fontSize": 14,
  "vsicons.dontShowNewVersionMessage": false,
  // 添加 vue 支持
  "eslint.validate": [
    "vue",
    "html",
    "javascript",
    "typescript",
    "javascriptreact",
    "typescriptreact",
    "jsx",
    {
      "language": "vue",
      "autoFix": true
    },
    {
      "language": "html",
      "autoFix": true
    },
    {
      "language": "javascript",
      "autoFix": true
    },
    {
      "language": "typescript",
      "autoFix": true
    },
    {
      "language": "jsx",
      "autoFix": true
    }
  ],
  "git.enableSmartCommit": true,
  "leek-fund.fundSort": -1,
  "editor.tabSize": 2,
  "editor.guides.indentation": false,
  "settingsSync.ignoredExtensions": [],
  "[typescript]": {
    "editor.defaultFormatter": "vscode.typescript-language-features"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "vscode.typescript-language-features"
  },
  "leek-fund.fundGroups": ["我的基金", "南方证券", "004070"],
  "leek-fund.funds": [
    [
      "001632",
      "420009",
      "320007",
      "003096",
      "001102",
      "003885",
      "001071",
      "005963"
    ],
    [],
    []
  ],
  "cSpell.languageSettings": [
    
 
 
  
  ],
  /*
  * 注释配置
  */
  "fileheader.configObj": {
    "autoAdd": false, // 默认开启自动添加头部注释,
  },
  // 注释(mac 版本: ctrl + command + i)
  "fileheader.customMade": { // 此为文件头部注释
    "Author": "", // 创建人 写上自己的名字
    "Date": "Do not edit", // 创建时间
    "LastEditors": "your Name", // 最后一次编辑人 写上自己的名字
    "LastEditTime": "Do not edit", // 最后一次编辑时间
    "Description": "" // 文件描述 生成注释后在手动编辑
  },
  // 注释(mac 版本: ctrl + command + t)
  "fileheader.cursorMode": { //此为函数注释
    "description": "", // 函数功能描述 生成注释后在手动编辑
    "param": "", // 参数 生成注释后在手动编辑
    "return": "", // 返回值 生成注释后在手动编辑
    "author": "your Name" // 创建人 写上自己的名字
  },
 
  // #去掉代码结尾的分号
  "prettier.semi": false,
  //  #使用带引号替代双引号
  "prettier.singleQuote": true,
  "html.format.maxPreserveNewLines": 120,
  "html.format.enable": false,
  "prettier.bracketSpacing": true,
  "prettier.htmlWhitespaceSensitivity": "ignore",
  "prettier.tabWidth": 2,
  //将>多行JSX元素放在最后一行的末尾,而不是单独放在下一行
  "prettier.jsxBracketSameLine": true,
  "prettier.printWidth": 120, //一行的代码数
  "vetur.format.defaultFormatter.html": "js-beautify-html",
  "vetur.format.defaultFormatter.js": "vscode-typescript",
  "vetur.format.defaultFormatter.css": "prettier",
  "vetur.format.options.tabSize": 2,
  "vetur.format.options.useTabs": false,
  "vetur.format.defaultFormatterOptions": {
    "js-beautify-html": {
      // js-beautify-html settings here
      "wrap_attributes": "auto", // 属性强制折行对齐,也可以设置为“auto”,force-aligned,效果会不一样
      "wrap_line_length": 120, // 设置一行多少字符换行,设置为 0 表示不换行
      "end_with_newline": false,
      "semi": false, //
      "singleQuote": true, // 单引号
      // "max-preserve-newlines": 500, // 一次可保留的最大换行数
      // "indent-inner-html": true, //缩进 head body代码片段
      // "editorconfig": true //使用editorconfig设置选项
    },
    "prettyhtml": {
      "printWidth": 120,
      "singleQuote": false,
      "wrapAttributes": false,
      "sortAttributes": false
    },
    "prettier": {
      // Prettier option here
      // "trailingComma": "es5", // 多行时,尽可能打印尾随的逗号
      "printWidth": 120,
      "tabWidth": 2, // 会忽略vetur的tabSize配置
      "useTabs": false, // 是否利用tab替代空格
      "semi": false, // 句尾是否加;
      "singleQuote": true // 使用单引号而不是双引号
      // "arrowParens": "avoid" // allow paren-less arrow functions 箭头函数的参数使用圆括号
    }
  },
  "vetur.validation.template": false,
  // "vetur.experimental.templateInterpolationService": true,
  //  #让函数(名)和后面的括号之间加个空格
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  "javascript.updateImportsOnFileMove.enabled": "always",
  // "diffEditor.renderSideBySide": true,
  "diffEditor.ignoreTrimWhitespace": false,
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[less]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "octref.vetur"
  },
  "launch": {
    "configurations": [
    
    ]
  },
}

思维方法--常用算法等

通过地址传递保存共用数据

 

函数传参 防止死循环

 

 数据左右循环移动

//SPI发送和接收数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;

	for(i = 0; i < 8; i++)
	{
		MySPI_W_MOSI(ByteSend & 0x80); //每次发送1个位 发八次 高位先发
		ByteSend <<= 1;
		MySPI_W_CLK(1);
		if(MySPI_R_MISO() == 1)				  //从机有反应
		{
			ByteSend |= 0x01;//每次获取最高位 然后往左偏移
		}
		MySPI_W_CLK(0);		
	}
	
	return ByteReceive;
}

32、24、16、8位数据处理

发送高位和读取低位方法 

八进制和unix时间撮 

按键消抖问题 

 按键短按,长按,双击

这个函数是按钮事件的核心处理逻辑,通过读取按钮的当前状态并结合时间戳来判断按钮事件的类型。具体步骤如下:

  1. BUTTON_IDLE: 如果按钮从空闲状态变为按下状态,记录按下的时间戳并更新状态为BUTTON_PRESSED
  2. BUTTON_PRESSED: 如果按钮从按下状态变为释放状态,记录释放的时间戳并计算按下的持续时间。根据持续时间判断是单击、长按还是进入双击等待状态。
  3. BUTTON_DOUBLE_CLICK_WAIT: 如果按钮在双击等待状态下再次被按下,检查两次按下的时间间隔是否小于双击最大时间间隔。如果是,则触发双击事件并回到初始状态;否则,超时后回到初始状态。
#define SINGLE_CLICK_TIME    300  // 单击最大时间间隔(ms)
#define DOUBLE_CLICK_TIME    600  // 双击最大时间间隔(ms)
#define LONG_PRESS_TIME      1500 // 长按时间(ms)

// 定义按钮状态的枚举类型
typedef enum {
    BUTTON_IDLE,            // 按钮空闲状态
    BUTTON_PRESSED,         // 按钮按下状态
    BUTTON_RELEASED,        // 按钮释放状态
    BUTTON_DOUBLE_CLICK_WAIT // 等待双击状态
} ButtonState;

// 定义全局变量,用于记录按钮状态和时间戳
volatile ButtonState buttonState = BUTTON_IDLE;
volatile uint32_t buttonPressTime = 0;
volatile uint32_t buttonReleaseTime = 0;


void ButtonHandler(void) {
    uint8_t currentButtonState = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0); // 读取当前按钮状态

    switch (buttonState) {
        case BUTTON_IDLE:
            if (currentButtonState == GPIO_PIN_RESET) {  // 按键按下
                buttonPressTime = HAL_GetTick();         // 记录按下的时间戳
                buttonState = BUTTON_PRESSED;            // 更新按钮状态为按下
            }
            break;

        case BUTTON_PRESSED:
            if (currentButtonState == GPIO_PIN_SET) {  // 按键松开
                buttonReleaseTime = HAL_GetTick();     // 记录松开的时间戳
                uint32_t pressDuration = buttonReleaseTime - buttonPressTime; // 计算按下持续时间

                if (pressDuration < SINGLE_CLICK_TIME) {                    // 单击事件
                    HandleSingleClick();                                    // 调用单击处理函数
                } else if (pressDuration >= LONG_PRESS_TIME) {              // 长按事件
                    HandleLongPress();                                      // 调用长按处理函数
                } else {                                                    // 进入双击等待状态
                    buttonState = BUTTON_DOUBLE_CLICK_WAIT;                 // 更新按钮状态为双击等待
                }
            }
            break;

        case BUTTON_DOUBLE_CLICK_WAIT:
            if (currentButtonState == GPIO_PIN_RESET) {                // 再次检测到按键按下
                uint32_t currentTime = HAL_GetTick();                  // 获取当前时间戳
                if (currentTime - buttonReleaseTime < DOUBLE_CLICK_TIME) { // 如果两次按下时间间隔小于双击最大时间间隔
                    HandleDoubleClick();                                // 调用双击处理函数
                    buttonState = BUTTON_IDLE;                          // 处理完后回到初始状态
                } else {                                                // 超过双击最大时间间隔,回到初始状态
                    buttonState = BUTTON_IDLE;
                }
            }
            break;
    }
}

通过位判断设置值

 

获取个位十位百位 

 

for敲桌子游戏 

#include <iostream>
using namespace std;
int main()
{
	// 1. 0~100敲桌子游戏
		// 1.1 7的倍数num%7 == 0	 7 14 21 28....
		// 1.2 个位有7num%10 == 7	 7 17 27 37....
		// 1.3 十位有7 num/10 == 0   70 71 72 73 ...

	for (int i = 1; i < 100; i++) {
		if (i % 7 == 0 || i % 10 == 7 || i / 10 == 7) {
			cout << "敲桌子" << endl;
		}
		else {
			cout << i << endl;
		}
	}
	return 0;
}

 一维数组逆置 

#include <iostream>
using namespace std;
int main()
{
	// 1.创建数组
	int arr[] = { 1,3,5,7,8,6,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i];
	}
	cout << endl;

	// 2.实现逆置
		// 2.1 记录起始下标位置
		// 2.2 记录结束下标位置
		// 2.3 通过temp交换数据
		// 2.4 循环交换数据 条件是start < end
	int start = 0;
	int end = sizeof(arr) / sizeof(arr[0]) - 1;
	int temp;
	while (start < end) {	//如果前面小标小于后面下标 则交换数据后前面下标往后移1格 后面下标往前移1格
		temp = arr[start];
		arr[start] = arr[end];
		arr[end] = temp;
		start++;
		end--;
	}
	
	// 3.打印逆置数组
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i];
	}
	return 0;
}


#include <stdio.h>
int main()
{
	int arr[] = {1,16,5,3,4,8,9};
	
	for(int i = 0;i<sizeof(arr)/sizeof(arr[0]);i++){
		printf("%d\t",arr[i]);
	}
	putchar('\n');
	
	
	int* tp = arr;
	int* wp = &arr[6];
	int temp;
	while(tp < wp){
		temp = *tp;
		*tp = *wp;
		*wp = temp;
		tp++;
		wp--;
	}
	
		
	for(int i = 0;i<sizeof(arr)/sizeof(arr[0]);i++){
		printf("%d\t",arr[i]);
	}
	putchar('\n');
	
	return 0;
 } 

按键消抖 

   /*开机按键 key_flag_memory消抖 按下时进来key_flag_memory=ON,再次进来判断还是ON,复位OFF,再key_flag = ON*/

if( KeyStatus() == ON )  //按键按下
    {
        if(key_flag_memory == ON)
        {
            key_flag_memory = OFF;
            key_flag = ON;
        }
        else
        {
            key_flag_memory = ON;
        }
    }
    else
    {
        key_flag_memory = OFF;
    }

 冒泡排序

#include <iostream>
using namespace std;
int main()
{
	// 1.冒泡排序 两两比较 比n-1轮,每轮比较n-i-1个数
	int arr[11] = {1,2,6,55,9,8,12,4,6,17,16 };
	for (int i = 0; i < 11; i++) {
		cout << arr[i] << endl;
	}

	// 2.冒泡排序
	for (int i = 0; i < 11 - 1; i++) {			//比较n-1轮
		for (int j = 0; j < 11 - i - 1; j++) {	//每轮比较 n-1-i个数
			if (arr[j] > arr[j + 1]) {
				int temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
	}
	for (int i = 0; i < 11; i++) {
		cout << arr[i] << endl;
	}
	//
	return 0;
}

 

秒分钟小时进制计算 

//进入中断秒++ 秒++到60清零 分钟++ 到60清零 小时++
delay_second++;																							//秒++ 到60清零 分钟++																							
if( delay_second >= 600 )
{
		delay_second = 0;
		delay_minute ++;																				//分钟++ 到60清零 小时++	
		if(delay_minute >= 60) 																	
		{
				delay_minute = 0;																		
				delay_hour ++; 																			//小时++ 到10小时清0 关机
				if(delay_hour == 10){
					OzoneCtrl(OFF);FanCtrl(OFF);key_flag = 0;delay_second = 0;delay_minute = 0;delay_hour = 0;
				}
		}
}

C实现字节高低位互换

C语言如何实现字节高低位互换? (qq.com)

PWM翻转算法 

/*=====================================================
 * Function : void Time1_CCR_Set(uint8_t ccr_num , uint32_t temp )
 * Describe : time1的占空比数值定义
 * Input    : ccr_num:PWM通道号 red_ch  green_ch
              pwm_value 0~100;
 * Output   : none
 * Return   : none
 * Record   : 2022/04/18
 * 
=====================================================*/
void Time1_CCR_Set(enum_pwm_channel ccr_num , uint32_t pwm_value )
{
//    uint32_t temp = (100 - pwm_value)/2;    //低电平点亮led时 。高低电平翻转,正负占空比翻转
	uint32_t temp = pwm_value/2;
    if(temp > 48)  temp = 52; //防止数值溢出
    switch( ccr_num )  //判定PWM通道
    {
        case red_ch: 
            TIME1 -> CCR4 = temp;
        break;
        case green_ch:
            TIME1 -> CCR1 = temp;
        break;
        default:
            
        break;
    }
}


//breathe_value在定时器中++ 根据breathe_value的变化来调整红色LED灯的亮度,实现呼吸效果
//平方运算来减小占空比的变化幅度,使得呼吸效果更加平滑
//小于25时占空比++ 大于25时占空比减小 当breathe_value大于50时复位为0
void LedBreatheRed( void )
{
    uint16_t breathe_caculate = 0;     
    if( breathe_value < 25 )//25
    {
		//这里的平方运算是为了减小占空比的变化幅度,使得呼吸效果更加平滑
        breathe_caculate = (uint16_t)breathe_value * (uint16_t)breathe_value / 10;
        Time1_CCR_Set(red_ch , breathe_caculate);
    }
    else
    {
        breathe_caculate = (50 - breathe_value) * (50 - breathe_value) / 10;//50 -- 80
        Time1_CCR_Set(red_ch , breathe_caculate);
        if( breathe_value >= 50 ) // 80
        {
            breathe_value = 0;
        }
    }  
}



//breathe_value在定时器中++ 根据breathe_value的变化来调整红色LED灯的亮度,实现呼吸效果
//平方运算来减小占空比的变化幅度,使得呼吸效果更加平滑
//小于25时占空比++ 大于25时占空比减小 当breathe_value大于50时复位为0
void LedBreatheGreen( void )
{
    uint16_t breathe_caculate = 0;     
    if( breathe_value < 25 )
    {
        breathe_caculate = (uint16_t)breathe_value * (uint16_t)breathe_value / 10;
        Time1_CCR_Set(green_ch , breathe_caculate);
    }
    else
    {
        breathe_caculate = (50 - breathe_value) * (50 - breathe_value) / 10;
        Time1_CCR_Set(green_ch , breathe_caculate);
        if( breathe_value >= 50 )
        {
            breathe_value = 0;
        }
    }  
}

PWM呼吸灯2

//1.定时器10ms中断一次,time0_10ms置为1
//2.在主函数中PWM_10ms = 0;PWM_Count++
//3.调用PWM_SetCompare调整占空比

void Timer0Interrupt()		interrupt 1			   
{
    /*TIM0_it write here begin*/
    //Forbid editing areas between the labels !!!
    TIM0_Mode1SetReloadCounter(45536);
    /*TIM0_it write here*/
    /*<Generated by EasyCodeCube begin>*/
    if( time0_10ms == 0 )
    {
        time0_10ms = 1;
    }
	 if( PWM_10ms == 0 )
    {
        PWM_10ms = 1;
    }
    GroupBatValue();
    /*<Generated by EasyCodeCube end>*/
    /*Timer0Interrupt Flag Clear begin*/
    //Forbid editing areas between the labels !!!
    /*Timer0Interrupt Flag Clear end*/		
}

 //不断改变PWM占空比 充电不充电都需要呼吸灯
				if(PWM_10ms){
						//每10ms占空比++
						PWM_10ms = 0;
						PWM_Count+=2;
						if(PWM_Count==600){
							PWM_Count = 0;
						}
				}

//PWM呼吸灯控制函数 传入PWM通道
void PWM_SetCompare(PWM_OutputPin_TypeDef PWMx){
		 if(PWM_Count < 300){
			 		PWM_IndependentModeConfig(0x00|PWMx , PWM_Count);
		 }else{
			 		PWM_IndependentModeConfig(0x00|PWMx , 600-PWM_Count);			 
		 }
}

//PWM呼吸灯控制函数 传入PWM通道
void PWM_SetCompare(PWM_OutputPin_TypeDef PWMx){
         if(PWM_Count < 300){
                     PWM_IndependentModeConfig(0x00|PWMx , PWM_Count);
         }else{
                     PWM_IndependentModeConfig(0x00|PWMx , 600-PWM_Count);             
         }
}

ADC采样滤波

//中断职位 时间到采样,存放到数组中,采样十次,取平均值

                    	if( adc_flag ){
								adc_flag = 0;
//								adc_value=(ADCVH<<4)+(ADCVL>>4);
//								BAT_value = (float)adc_value * 5 / 4096 ;
								adc_value = ADC_GetConversionValue();
								BAT_value[Adc_Count] = (float)adc_value * 5 / 4096 ;//值存放在数组中
								Adc_Count++;
								if(Adc_Count==10){
									Adc_Count = 0;
									Average_BAT_value = getAverageBatValue(); //根据分压0.66/2.66 = 0.248   0.248*8.2=2.3V  0.248*7.5 = 1.85V
								}
						}

//通过AD采样计算电压值(均方根值法)后再取平均值  返回平均值
float getAverageBatValue(void)
{
    float sum = 0;
    for (i = 0; i < 10; i++)
    {
        sum += BAT_value[i];
    }
    return sum / 10;
}

 ADC采样算法优化

/**************************************************
*函数名称:uint MyAdc_Convert(void) 
*函数功能:ADC初始化
*入口参数:ADC_CHANNEL_x 传入ADC通道
*出口参数:void
**************************************************/
float MyAdc_Convert(ADC_Channel_TypeDef ADC_Channe_x)
{
    // 定义变量:Tad用于存储当前ADC转换值,MinAd和MaxAd分别用于存储最小和最大ADC值,TempAdd用于累加所有ADC值以便后续计算平均值。
	float Tad=0, MinAd=0xffff, MaxAd=0, TempAdd=0;
    // t为循环计数器
	uint8_t t=0;	
	//----------------------------------------
    // 循环18次进行ADC转换
	for(t=0; t<10; t++)
	{
		
		ADC_ChannelConfig(ADC_Channe_x,ENABLE);//通道2采样	
		Tad = ADC_GetConversionValue();//获得一次AD转换数据
		Tad = (float)Tad * 5.0 / 4096; 
															
		//----------------------------------------
        // 如果当前的ADC值大于之前记录的最大值,则更新最大值
		if (Tad > MaxAd)			
		{		
			MaxAd = Tad;
		}
		//---------------------------------------	
        // 如果当前的ADC值小于之前记录的最小值,则更新最小值------最小值待定
		if (Tad < MinAd)			
		{		
			MinAd = Tad;
		}
        // 累加当前ADC值到TempAdd中
		TempAdd += Tad;	
	}
    // 从累加的总和中减去最大值和最小值
	TempAdd -= MinAd;			
	TempAdd -= MaxAd;			
    // TempAdd除以16,得到平均值
	TempAdd = TempAdd/8;			
    // 返回计算出的平均值
	return(TempAdd); 
}

sprintf 和 snprintf

int sprintf(char *str, const char *format, ...);


#include <stdio.h>
int main() {    
char buffer[50];    
int value = 1234;    
double pi = 3.14159;
    
// 使用 sprintf 将格式化的数据写入 buffer    sprintf(buffer, "Value: %d, Pi: %.2f", value, pi);
    printf("Buffer contents: %s\n", buffer);
    return 0;}
int snprintf(char *str, size_t size, const char *format, ...);

#include <stdio.h>
int main() {    
char buffer[50];    
int value = 1234;    
double pi = 3.14159;
    
// 使用 snprintf 将格式化的数据写入 buffer    // 它检查 buffer 的大小以避免溢出   
 int written = snprintf(buffer, sizeof(buffer), "Value: %d, Pi: %.2f", value, pi);
    printf("Buffer contents: %s\n", buffer);    printf("Characters written: %d\n", written);
    return 0;}

主要区别

  1. 缓冲区大小:

    • sprintf 不关心目标缓冲区的大小,如果格式化后的字符串超过缓冲区的大小,会导致缓冲区溢出。

    • snprintf 允许你指定缓冲区的最大大小,如果格式化后的字符串超过这个大小,snprintf 会截断字符串以确保不会超出缓冲区。

  2. 返回值:

    • sprintf 返回写入的字符数,不包括结尾的空字符 \0

    • snprintf 也返回写入的字符数,但它会在目标缓冲区完全写入后(或被截断后)在末尾添加空字符 \0

  3. 安全性:

    • sprintf 在不知道目标缓冲区大小的情况下使用可能会导致安全问题。

    • snprintf 更加安全,因为它允许你控制写入的最大长度,从而避免缓冲区溢出。

状态机的三种实现方法

嵌入式软件,状态机的三种实现方法

状态机的实现无非就是 3 个要素:状态事件响应。转换成具体的行为就 3 句话。

  • 发生了什么事?

  • 现在系统处在什么状态?

  • 在这样的状态下发生了这样的事,系统要干什么?

用 C 语言实现状态机主要有 3 种方法:switch—case 法表格驱动法函数指针法

方法一  。出现频率高或者实时性要求高的状态和事件的位置应该尽量靠前。

switch(StateVal)
{
    case S0://状态1  时间  响应
switch(EvntID)
  {
   case E1:
    action_S0_E1(); /*S0 状态下 E1 事件的响应*/
    StateVal = new state value;/*状态迁移,不迁移则没有此行*/
    break;
   case E2:
    action_S0_E2(); /*S0 状态下 E2 事件的响应*/
    StateVal = new state value;
    break;
   ......
   case Em:
    action_S0_Em(); /*S0 状态下 Em 事件的响应*/
    StateVal = new state value;
    break;
   default:
    break;
  }
break;
    case S1://状态2
  ......
break;
    ......
    case Sn:
  ......
break;
    default:
break;
}

状态机简洁编程

// 定义状态枚举
typedef enum {
    HIGH_TEMP_HIGH_HUM,  // 高温高湿
    HIGH_TEMP_LOW_HUM,   // 高温低湿
    NORMAL               // 正常状态
} SystemState;
// 全局状态变量
volatile SystemState currentState = NORMAL;
// 硬件控制函数(单片机风格)
void turnOnFan() {
    PORTB |= (1 << PB0);  // 开启风扇
}
void turnOffFan() {
    PORTB &= ~(1 << PB0); // 关闭风扇
}
void turnOnHeater() {
    PORTB |= (1 << PB1);  // 开启加热器
}
void turnOffHeater() {
    PORTB &= ~(1 << PB1); // 关闭加热器
}
// 更新状态
void updateState(uint8_t temperature, uint8_t humidity) {
    if (temperature > 30 && humidity > 60) {
        currentState = HIGH_TEMP_HIGH_HUM;
    } else if (temperature > 25) {
        currentState = HIGH_TEMP_LOW_HUM;
    } else {
        currentState = NORMAL;
    }
}
// 系统控制
void controlSystem() {
    switch (currentState) {
        case HIGH_TEMP_HIGH_HUM:
            turnOnFan();
            turnOnHeater();
            break;
        case HIGH_TEMP_LOW_HUM:
            turnOnFan();
            turnOffHeater();
            break;
        case NORMAL:
            turnOffFan();
            turnOffHeater();
            break;
    }
}
// 主函数
int main(void) {
    DDRB |= (1 << PB0) | (1 << PB1);  // 配置PB0和PB1为输出
    uint8_t temp = 0, hum = 0;
    while (1) {
        temp = readTemperature();  // 假设的温度读取函数
        hum = readHumidity();      // 假设的湿度读取函数
        updateState(temp, hum);
        controlSystem();
    }
}

状态机加查表法


//定义状态

typedef enum {
    LOW,
    MEDIUM,
    HIGH
} SensorState;
SensorState getSensorState(int value) {
    if (value < 10) return LOW;
    else if (value < 20) return MEDIUM;
    else return HIGH;
}
//查表法

typedef void (*ProcessFunction)();
void processA() { /* 处理A */ }
void processB() { /* 处理B */ }
void processC() { /* 处理C */ }
void processD() { /* 处理D */ }
void processE() { /* 处理E */ }
void processF() { /* 处理F */ }
typedef struct {
    SensorState state;
    int mode;
    ProcessFunction func;
} ProcessTable;
const ProcessTable table[] = {
    {LOW, 1, processA},
    {LOW, 0, processB},
    {MEDIUM, 1, processC},
    {MEDIUM, 0, processD},
    {HIGH, 1, processE},
    {HIGH, 0, processF}
};
void processData(int sensorValue, int mode) {
    SensorState state = getSensorState(sensorValue);
    for (int i = 0; i < sizeof(table)/sizeof(table[0]); i++) {
        if (table[i].state == state && table[i].mode == mode) {
            table[i].func();
            return;
        }
    }
    // 未定义的处理
}

常见ADC滤波算法

参考文章 单片机ADC常用的十大滤波算法(C语言)

限幅滤波

/* A值根据实际调,Value有效值,new_Value当前采样值,程序返回有效的实际值 */
#define A 10
char Value;
char filter()
{
    char new_Value;
    new_Value = get_ad(); // 获取采样值
    if( abs(new_Value - Value) > A)   
        return Value;     // abs()取绝对值函数
    return new_Value;
}

中位值滤波

#define N 11
char filter()
{
    char value_buf[N];
    char count, i, j, temp;
    for(count = 0; count < N; count ++) //获取采样值
    {
        value_buf[count] = get_ad();
        delay();
    }
    for(j = 0; j < (N-1); j++)
    {
        for(i = 0; i < (n-j); i++)
        {
            if(value_buf[i] > value_buf[i+1])
            {
                temp = value_buf[i];
                value_buf[i] = value_buf[i+1];
                value_buf[i+1] = temp;
            }
        }
    }
    return value_buf[(N-1)/2];
}

平均值滤波

#define N 12
char filter()
{
    int sum = 0;
    for(count = 0; count < N; count++)
    {
        sum += get_ad();
    } 
    return (char)(sum/N);
}

单片机时间片轮询不同时间

参考博文:单片机开发常用的软件构架!

主函数

定时器中断中时间置位

 

 

函数指针设计方式

 创建结构体数组

 

 每次定时器中断1ms 数组的时间都--1,时间到将该标志位置1

 在主函数中不断遍历数组的标志位,标志位置1时执行函数指针 运行任务,之后将标志位清0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值