目录
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_5
、ADC_SAMPLETIME_7CYCLES_5
、ADC_SAMPLETIME_13CYCLES_5
、ADC_SAMPLETIME_28CYCLES_5
、ADC_SAMPLETIME_41CYCLES_5
、ADC_SAMPLETIME_71CYCLES_5
、ADC_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模式。以下是每个模式的含义:
- ADC_Mode_Independent:独立模式,不使用外部触发信号进行采样。
- ADC_Mode_RegInjecSimult:寄存器注入和模拟多通道模式。
- ADC_Mode_RegSimult_AlterTrig:寄存器模拟多通道模式,并使用外部触发信号进行采样。
- ADC_Mode_InjecSimult_FastInterl:注入和模拟多通道快速中断模式。
- ADC_Mode_InjecSimult_SlowInterl:注入和模拟多通道慢速中断模式。
- ADC_Mode_InjecSimult:注入和模拟多通道模式。
- ADC_Mode_RegSimult:寄存器模拟多通道模式。
- ADC_Mode_FastInterl:快速中断模式。
- ADC_Mode_SlowInterl:慢速中断模式。
- ADC_Mode_AlterTrig:使用外部触发信号进行采样的模式。
在配置ADC_Init时,需要根据具体需求和硬件平台设置一些参数。这些参数可能包括:
ADC_Mode:这个参数决定了ADC的工作模式,可以是独立模式、注入序列模式、扫描模式等。例如,如果设置为STM32的ADC_Mode_Independent,则双ADC不能同步,每个ADC接口独立工作。
ADC_ScanConvMode:此参数决定了ADC的扫描转换方式,可以是单次扫描模式或连续扫描模式。
ADC_ContinuousConvMode:决定是否启用连续转换模式。当需要实时连续转换时可以设置为ENABLE,但这可能会消耗更多的CPU资源。
ADC_ExternalTrigConv:此参数用于指定是否使用外部触发信号来启动转换。
ADC_NbrOfChannel:这个参数决定了规则转换的ADC通道的数量。
ADC_InitStructure.ADC_DataAlign:此参数决定了数据对齐方式,可以是左对齐或右对齐。
ADC_InitStructure.ADC_Resolution:此参数决定了ADC的分辨率。
ADC_InitStructure.ADC_ContinuousConvMode:决定是否启用连续转换模式。
ADC_InitStructure.ADC_ExternalTrigConv:此参数用于指定是否使用外部触发信号来启动转换。
这些函数是用于ADC(模数转换器)的校准和状态查询的。下面是每个函数的功能解释:
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
:这个函数用于重置ADC的校准寄存器,以便进行新的校准。它接受一个指向ADC实例的指针作为参数。
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
:这个函数用于获取ADC的重置校准状态。它返回一个标志状态,指示是否成功完成了重置校准。它接受一个指向ADC实例的指针作为参数。
void ADC_StartCalibration(ADC_TypeDef* ADCx);
:这个函数用于开始ADC的校准过程。它接受一个指向ADC实例的指针作为参数。
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)
{
}
}
DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx)
:此函数用于取消初始化指定的DMA通道。在调用此函数后,该通道将不再使用,可以重新配置或释放资源。
DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
:此函数用于初始化指定的DMA通道。它接受一个指向DMA通道的指针和一个指向DMA初始化结构体的指针作为参数。通过这个函数,你可以设置DMA通道的各种属性,如传输模式、数据大小、优先级等。
DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct)
:此函数用于初始化DMA初始化结构体。在调用此函数后,你可以使用它来设置DMA通道的各种属性。
DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState)
:此函数用于启动或停止指定的DMA通道。它接受一个指向DMA通道的指针和一个表示新状态的枚举值作为参数。如果新状态为ENABLE,则启动DMA通道;如果新状态为DISABLE,则停止DMA通道。
DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState)
:此函数用于配置指定DMA通道的中断。它接受一个指向DMA通道的指针、一个表示中断类型的枚举值和一个表示新状态的枚举值作为参数。通过这个函数,你可以设置DMA通道何时触发中断。
DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)
:此函数用于设置指定DMA通道的当前数据计数器值。它接受一个指向DMA通道的指针和一个表示数据数量的无符号整数作为参数。通过这个函数,你可以控制DMA通道传输的数据量。
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
:此函数用于获取指定DMA通道的当前数据计数器值。它接受一个指向DMA通道的指针作为参数,并返回一个表示数据数量的无符号整数。通过这个函数,你可以查询DMA通道传输的数据量。
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
:此函数用于获取指定标志的状态。它接受一个表示标志的无符号整数作为参数,并返回一个表示标志状态的枚举值。通过这个函数,你可以检查DMA通道是否触发了某个事件。
void DMA_ClearFlag(uint32_t DMAy_FLAG)
:此函数用于清除指定标志的状态。它接受一个表示标志的无符号整数作为参数。通过这个函数,你可以重置DMA通道的事件状态。
ITStatus DMA_GetITStatus(uint32_t DMAy_IT)
:此函数用于获取指定中断的状态。它接受一个表示中断的无符号整数作为参数,并返回一个表示中断状态的枚举值。通过这个函数,你可以检查DMA通道是否触发了某个中断。
void DMA_ClearITPendingBit(uint32_t DMAy_IT)
:此函数用于清除指定中断的标志位。它接受一个表示中断的无符号整数作为参数。通过这个函数,你可以重置DMA通道的中断状态。
DMA_InitTypeDef 是一个结构体,用于初始化 DMA 控制器。它包含了以下参数:
- DMA_PeripheralBaseAddr:指定 DMAy Channelx 的外设基地址。
- DMA_MemoryBaseAddr:指定 DMAy Channelx 的内存基地址。
- DMA_DIR:指定 DMA 传输方向,可以是 DMA_PERIPHERAL_TO_MEMORY(外设到内存)或 DMA_MEMORY_TO_PERIPHERAL(内存到外设)。
- DMA_BufferSize:指定 DMA 缓冲区的大小,单位为字节。
- DMA_PeripheralInc:指定外设地址增量,可以是 DMA_PERIPHERAL_INCREASE(外设地址自增)或 DMA_PERIPHERAL_NO_INCREASE(外设地址不变)。
- DMA_MemoryInc:指定内存地址增量,可以是 DMA_MEMORY_INCREASE(内存地址自增)或 DMA_MEMORY_NO_INCREASE(内存地址不变)。
- DMA_PeripheralDataSize:指定外设接收或发送的数据大小,可以是 DMA_PERIPHERAL_DATA_SIZE_BYTE(一个字节)、DMA_PERIPHERAL_DATA_SIZE_HALFWORD(半个字)、DMA_PERIPHERAL_DATA_SIZE_WORD(一个字)等。
- DMA_MemoryDataSize:指定内存接收或发送的数据大小,可以是 DMA_MEMORY_DATA_SIZE_BYTE(一个字节)、DMA_MEMORY_DATA_SIZE_HALFWORD(半个字)、DMA_MEMORY_DATA_SIZE_WORD(一个字)等。
- DMA_Mode:指定 DMA 工作模式,可以是 DMA_NORMAL(正常模式)、DMA_CIRCULAR(循环模式)等。
- 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模式包括:
- 模拟输入模式(GPIO_Mode_AIN):关闭施密特触发器,将电压信号传送到片上外设模块,不接上下拉电阻。
- 浮空输入模式(GPIO_Mode_IN_FLOATING):没有接上下拉电阻的输入状态。
- 下拉输入模式(GPIO_Mode_IPD):有接下拉电阻的输入状态。
- 上拉输入模式(GPIO_Mode_IPU):有接上拉电阻的输入状态。
- 开漏输出模式(GPIO_Mode_Out_OD):输出状态由外部电路控制,无法真正输出高电平或低电平。
- 推挽输出模式(GPIO_Mode_Out_PP):可以真正输出高电平和低电平,IO口的输出状态由输出寄存器决定。
- 复用开漏输出模式:输出状态由外部电路控制,无法真正输出高电平或低电平,但可以用作第二功能。
- 复用推挽输出模式:可以真正输出高电平和低电平,且可以用作第二功能。
提取数字小技巧
//与下面函数配合 用/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);
}
}
}
FlyMCU串口下载和stlink
I2C传输
基础通识
I2C协议的工作原理可以概括为以下几个步骤:
起始信号:由主机发出,它是一个高电平信号。这个信号会通知所有被寻址的器件,它们都被看作是从机。
地址传输:主机接着发送一个包含7位地址和一位读写控制位的数据包。每个连接到总线的设备都有唯一的地址,接收到这个数据包的设备会根据地址判断是否是自己,如果是,它会作出响应。
数据传输:如果设备决定响应主机的请求(无论是读还是写),它会继续发送一个应答信号,然后开始数据传输。对于写操作,主机将数据发送到从机;对于读操作,从机将数据发送到主机。
确认信号:每当一个字节的数据被传输完成,接收方会产生一个应答信号来通知发送方数据已经被正确接收。发送方在收到确认信号后才能停止发送数据。
终止信号:数据传输结束后,主机会发出一个终止信号,这是一个低电平信号。所有的通信过程都会以此信号作为结束标志。
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进行初始化配置,它包含以下参数:
uint32_t I2C_ClockSpeed:设置SCL时钟频率,此值需不低于40000。另外,有的资料指出其频率不得高于400kHz,这可能与具体硬件和库有关,需要根据实际情况设置。
uint16_t I2C_Mode:用于指定工作模式,可以选择I2C模式或SMBus主从模式。
uint16_t I2C_DutyCycle:用于指定时钟占空比,可以选择低/高=2:1以及16:9模式。
uint16_t I2C_OwnAddress1:表示I2C设备自身的地址。
FunctionalState I2C_Ack:应答使能,若使能后可以发送响应信号。
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时间撮
按键消抖问题
按键短按,长按,双击
这个函数是按钮事件的核心处理逻辑,通过读取按钮的当前状态并结合时间戳来判断按钮事件的类型。具体步骤如下:
- BUTTON_IDLE: 如果按钮从空闲状态变为按下状态,记录按下的时间戳并更新状态为
BUTTON_PRESSED
。- BUTTON_PRESSED: 如果按钮从按下状态变为释放状态,记录释放的时间戳并计算按下的持续时间。根据持续时间判断是单击、长按还是进入双击等待状态。
- 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实现字节高低位互换
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;}
主要区别
缓冲区大小:
sprintf
不关心目标缓冲区的大小,如果格式化后的字符串超过缓冲区的大小,会导致缓冲区溢出。
snprintf
允许你指定缓冲区的最大大小,如果格式化后的字符串超过这个大小,snprintf
会截断字符串以确保不会超出缓冲区。返回值:
sprintf
返回写入的字符数,不包括结尾的空字符\0
。
snprintf
也返回写入的字符数,但它会在目标缓冲区完全写入后(或被截断后)在末尾添加空字符\0
。安全性:
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