STM32 ---- 02 再次学习STM32F103C8T6/STM32F409IGT6

目录

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);
	}
}

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

 

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

 

函数传参 防止死循环

 

 数据左右循环移动

//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时间撮 

按键消抖问题 

通过位判断设置值

 

获取个位十位百位 

 

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值