单片机学习大总结

单片机怎么学?保姆级学习路线(附教程+工具) 这篇文章,就是给大家一个具体可实操的,从入门到就业最快的自学路线,从职业定位到不同阶段学什么内容,直接把饭喂到你嘴里,无论是科班学生,跨行自学、都能轻松上手。我做了10几年,基本没用过汇编,汇编的应用场景,大概就2个,第一个是移植RTOS的时候,第二个产品对成本要求非常苛刻,使用的单片机资源非常非常少的时候。51单片机在家用电器、工业控制、汽车电子等领域有广泛的应用,很简单的道理,51成本低,简单,如果条件允许,企业巴不得把STM32的产品线全部换成51的。 阅读详情

声明:文章内容来自网络总结,站在巨人肩膀上我们可以看得更远。

更新说明:更新了一些代码

本文总结了作者两年半一来的学习内容,时间关系还没完成,后续将继续更新,欢迎一起分享交流

1、GPIO控制

GPIO的八种工作模式

浮空输入

数字输入

可读取引脚电平,若引脚悬空,则电平不确定

上拉输入

数字输入

可读取引脚电平,内部连接上拉电阻,悬空时默认高电平

下拉输入

数字输入

可读取引脚电平,内部连接下拉电阻,悬空时默认低电平

模拟输入

模拟输入

GPIO无效,引脚直接接入内部ADC

开漏输出

数字输出

可输出引脚电平,高电平为高阻态,低电平接VSS

推挽输出

数字输出

可输出引脚电平,高电平接VDD,低电平接VSS

复用开漏输出

数字输出

由片上外设控制,高电平为高阻态,低电平接VSS

复用推挽输出

数字输出

由片上外设控制,高电平接VDD,低电平接VSS

 常用于读取电平、输出电平,如点亮LED灯,按键检测,模拟电压输入输出等。

常见的小项目:LED闪烁、呼吸灯、流水灯、数码管显示、矩阵按键扫描、蜂鸣器报警

LED闪烁、蜂鸣器报警代码

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

int main(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	//开启GPIOA的时钟
															//使用各个外设前必须开启时钟,否则对外设的操作无效
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				//GPIO引脚,赋值为第0号引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
	
	GPIO_Init(GPIOC, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
															//函数内部会自动根据结构体的参数配置相应寄存器
															//实现GPIOA的初始化
	
	/*主循环,循环体内的代码会一直循环执行*/
	while (1)
	{
		/*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*/
		
		/*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/
		GPIO_ResetBits(GPIOC, GPIO_Pin_13);					//将PA0引脚设置为低电平
		Delay_ms(500);										//延时500ms
		GPIO_SetBits(GPIOC, GPIO_Pin_13);					//将PA0引脚设置为高电平
		Delay_ms(500);										//延时500ms
		
		/*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/
//		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);		//将PA0引脚设置为低电平
//		Delay_ms(500);										//延时500ms
//		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);			//将PA0引脚设置为高电平
//		Delay_ms(500);										//延时500ms
//		
//		/*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction类型*/
//		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);		//将PA0引脚设置为低电平
//		Delay_ms(500);										//延时500ms
//		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);		//将PA0引脚设置为高电平
//		Delay_ms(500);										//延时500ms
	}
}

呼吸灯即为改变LED灯在一个周期内接通的时间即可实现,流水灯则通过C语言的移位来实现

for(i=0;i<=8;i++)
{
GPIO_Write(GPIOA, ~0x01<<i);
}

数码管则需考虑对应的码值

GPIO_Write(GPIOA, 0xfe);//数字8

 矩阵按键扫描

因为stm32单片机不常用矩阵按键,这里以51单片机的代码举例

/////////////////////////////////////////////////////	
	P3=0xFF;P3_0=0;          //片选
	if(P3_4==0){KeyNumber=1;}//位选
	if(P3_5==0){KeyNumber=5;}//位选
	if(P3_6==0){KeyNumber=9;}//位选
	if(P3_7==0){KeyNumber=13;}//位选

	
	P3=0xFF;P3_1=0; 
	if(P3_4==0){KeyNumber=2;}
	if(P3_5==0){KeyNumber=6;}
	if(P3_6==0){KeyNumber=10;}
	if(P3_7==0){KeyNumber=14;}
	
	P3=0xFF;P3_2=0;	
	if(P3_4==0)	{KeyNumber=3;}
	if(P3_5==0)	{KeyNumber=7;}
	if(P3_6==0){KeyNumber=11;}
	if(P3_7==0){KeyNumber=15;}
	P3=0xFF;P3_3=0;
	if(P3_4==0)	{KeyNumber=4;}	
	if(P3_5==0){KeyNumber=8;}
	if(P3_6==0){KeyNumber=12;}
	if(P3_7==0){KeyNumber=16;}

2、中断系统

中断系统包括外部中断和定时器定时中断。常见的中断通道有EXTI(外部中断)、TIM(定时中断)、ADC(ADC采样与输出)、USART(串口中断)、SPI(spi通信)、I2C(iic通信)、RTC(实时时钟)

外部中断常用于:红外遥控,编码计数器计数

ADC

• ADC ( Analog-Digital Converter )模拟 - 数字转换器
• ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
• 12 位逐次逼近型 ADC , 1us 转换时间
• 输入电压范围: 0~3.3V ,转换结果范围: 0~4095
• 18 个输入通道,可测量 16 个外部和 2 个内部信号源
• 规则组和注入组两个转换单元
• 模拟看门狗自动监测输入电压范围

DMA 

DMA区别于中断系统,它拥有一个独立的运算空间,不需要消耗cpu的运算资源

3、定时器

定时器类型

stm32f103c8t6的定时器类型如下

类型

编号

总线

功能

高级定时器

TIM1、TIM8

APB2

拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能

通用定时器

TIM2、TIM3、TIM4、TIM5

APB1

拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能

基本定时器

TIM6、TIM7

APB1

拥有定时中断、主模式触发DAC的功能

定时器常用于计时、中断等

4、电机控制(pid)

pwm

• PWM ( Pulse Width Modulation )脉冲宽度调制
• 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
• PWM 参数:频率 = 1 / T S            占空比 = T ON / T S           分辨率 = 占空比变化步距
名词解析:psc:预分频   arr自动重装值
• 频率 :   Freq = CK_PSC / (PSC + 1) / (ARR + 1)
• PWM 占 空 PWM 比 :   Duty = CCR / (ARR + 1)
• PWM 分辨率:   Reso = 1 / (ARR + 1)

pwm的运用——舵机

• 舵机是一种根据输入 PWM 信号占空比来控制输出角度的装置
• 输入 PWM 信号要求:周期为 20ms ,高电平宽度为 0.5ms~2.5ms

红色接+,黑色接-,黄色/棕色接pwm信号线 

 参考代码

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7| GPIO_Pin_8| GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM4);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
	TIM_OC1Init(TIM4, &TIM_OCInitStructure);
	TIM_OC2Init(TIM4, &TIM_OCInitStructure);
	TIM_OC3Init(TIM4, &TIM_OCInitStructure);
	TIM_OC4Init(TIM4, &TIM_OCInitStructure);
	
	TIM_Cmd(TIM4, ENABLE);
}

void Servo_SetAngle(uint8_t oc,float Angle)
{
	switch(oc)
	{
		case 1:	TIM_SetCompare1(TIM4, (Angle / 270 * 2000 + 500) );
		case 2:	TIM_SetCompare2(TIM4, (Angle / 270 * 2000 + 500) );
		case 3:	TIM_SetCompare3(TIM4, (Angle / 270 * 2000 + 500) );
		case 4:	TIM_SetCompare4(TIM4, (Angle / 270 * 2000 + 500) );
	}
}

//  调用
//  	Servo_SetAngle(1,90);//通道,角度
//		Servo_SetAngle(2,90);//通道,角度

pwm的运用——直流减速电机

• 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
• 直流电机属于大功率器件, GPIO 口无法直接驱动,需要配合电机驱动电路来操作
• TB6612 是一款双路 H 桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

 

 pwm初始化

void PWM_Init(u16 arr,u16 psc) //定时器TIM1pwm通道初始化
{		 		
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);  //使能GPIO外设时钟使能
   //设置该引脚为复用输出功能,输出TIM1 CH1 CH4的PWM脉冲波形
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 //TIM_CH4
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OC4Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

  TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能	

	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
	TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH4预装载使能	 
	
	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_Cmd(TIM1, ENABLE);  //使能TIM1

} 

电机控制脚初始化

void Pinout_Init(void)				//引脚B12,B13,B14,B15初始化
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

 

(在sys.h中有将引脚宏定义成Ain形式)

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
#define Ain1  PBout(12)
#define Ain2  PBout(13)

#define Bin1  PBout(14)
#define Bin2  PBout(15)

 电机控制代码参考,记得做限幅处理

void setpwm(int motor_left, int motor_right)
{
	if(motor_left>0)
	{		
		Ain1=1;
		Ain2=0; 
		TIM_SetCompare1(TIM1,motor_left);
	}//前进 
	else   
	{
		Ain1=0;
		Ain2=1; //后退
		TIM_SetCompare1(TIM1,-motor_left);
		
	}		
	if(motor_right>0)
	{		
		Bin1=1;
		Bin2=0; 
		TIM_SetCompare4(TIM1,motor_right);
	}//前进 
	else   
	{
		Bin1=0;
		Bin2=1; //后退
		TIM_SetCompare4(TIM1,-motor_right);
		
	}		
}

编码器的使用 

• Encoder Interface 编码器接口
• 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制 CNT 自增或自减,从而指示编码器的位置、旋转方向和旋转速度
• 每个高级定时器和通用定时器都拥有 1 个编码器接口
• 两个输入引脚借用了输入捕获的通道 1 和通道 2

 

 编码器代码参考

使用定时器的编码器模式

/**************************************************************************
*  函数功能:把TIM2初始化为编码器接口模式
*
*  入口参数:无
*
*  返 回 值:无
**************************************************************************/
void Encoder_Init_TIM2(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
	TIM_ICInitTypeDef TIM_ICInitStructure;  
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA PB端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;	//端口配置 PA15
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOA
  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	//端口配置 PB3
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB
	
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 
	TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//边沿计数模式 
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);  //初始化定时器2
	
	TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
	
	TIM_ICStructInit(&TIM_ICInitStructure); //把TIM_ICInitStruct 中的每一个参数按缺省值填入
	TIM_ICInitStructure.TIM_ICFilter = 10;  //设置滤波器长度
	TIM_ICInit(TIM2, &TIM_ICInitStructure);//根据 TIM_ICInitStruct 的参数初始化外设	TIMx
 
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//使能定时器中断
	
	TIM_SetCounter(TIM2,0);
	TIM_Cmd(TIM2, ENABLE); //使能定时器2
}

/**************************************************************************
函数功能:把TIM3初始化为编码器接口模式
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM3(void)
{
  	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
  	TIM_ICInitTypeDef TIM_ICInitStructure;  
  	GPIO_InitTypeDef GPIO_InitStructure;
	
  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3的时钟
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口时钟
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	//端口配置 PA6 PA7
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  	GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOA
  	
  	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 
 	TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
  	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
  	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//边沿计数模式 
  	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);  //初始化定时器3
	
  	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3(TIM_ICPolarity_Rising或者TIM_ICPolarity_Falling效果相同,都是4倍频)
	
  	TIM_ICStructInit(&TIM_ICInitStructure); //把TIM_ICInitStruct 中的每一个参数按缺省值填入
  	TIM_ICInitStructure.TIM_ICFilter = 10;  //设置滤波器长度
  	TIM_ICInit(TIM3, &TIM_ICInitStructure);//根据 TIM_ICInitStruct 的参数初始化外设	TIMx
 
  	TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
  	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//使能定时器中断
  	TIM_SetCounter(TIM3,0);
  	TIM_Cmd(TIM3, ENABLE); //使能定时器
}

/**************************************************************************
函数功能:单位时间读取编码器A计数
入口参数:无
返回  值:计数值
**************************************************************************/
float Read_EncoderA(void)
{
	int Encoder_TIM = 0;  
	float Speed = 0.0;  
	Encoder_TIM= (short)TIM2 -> CNT;   
	TIM2 -> CNT=0;
	return -Encoder_TIM;
}
/**************************************************************************
函数功能:单位时间读取编码器B计数
入口参数:无
返回  值:计数值
**************************************************************************/
float Read_EncoderB(void)
{
	int Encoder_TIM = 0;  
	float Speed = 0.0;  
	Encoder_TIM= (short)TIM3 -> CNT;   
	TIM3 -> CNT=0;
	return Encoder_TIM;
}

/**************************************************************************
*  函数功能:TIM2中断服务函数
*
*  入口参数:无
*
*  返 回 值:无
**************************************************************************/
void TIM2_IRQHandler(void)
{ 		    		  			    
	if(TIM2->SR&0X0001)//溢出中断
	{    				   				     	    	
	}				   
	TIM2->SR&=~(1<<0);//清除中断标志位 	    
}


/**************************************************************************
*  函数功能:TIM3中断服务函数
*
*  入口参数:无
*
*  返 回 值:无
**************************************************************************/
void TIM3_IRQHandler(void)
{ 		    		  			    
	if(TIM3->SR&0X0001)//溢出中断
	{    				   				     	    	
	}				   
	TIM3->SR&=~(1<<0);//清除中断标志位 	    
}





int Read_Spead(int TIMx)
{
	int value_1;
	switch(TIMx)
	{
		case 2:value_1 = (short)TIM_GetCounter(TIM2);TIM_SetCounter(TIM2, 0);break;
		case 3:value_1 = (short)TIM_GetCounter(TIM3);TIM_SetCounter(TIM4, 0);break;
		default:value_1 = 0;
	}
	return value_1;
}

 

pwm+定时器编码=pid闭环控制 

pid控制由输入和输出两部分组成,编码器和定时器组合可以得到电机运动的情况,如转数、转速等作为输入,单片机通过调节pwm输出,从而实现对电机的精确控制。

p比例  i积分  d微分 

pid的理解:pid实际上是一种强制相等的算法,速度值通过pid运算竟然能得到对应的pwm占空比,这件事情本身就很神奇,其原理无非就是通过pid控制器让它们两个变量强制相等,多少占空比对应多少速度。

如果再加上其他外设,即可实现对应的功能,如加上陀螺仪可以制作平衡小车、直线行驶小车(电赛题)等,加上蓝牙模块可以做蓝牙遥控小车,手势小车等

平衡小车

平衡小车的思路如下:

由陀螺仪输入角度信息,由于平衡的需要,陀螺仪数据更新的周期应当越短越好,通常为5ms。

将陀螺仪数据显示在OLED上,然后前后倾倒平衡小车,看看哪个数据变化最大,将其作为直立环的输入。直立环调参主要调节p,调节到平衡小车快速抖动,停止调节。调节i可以减小抖动。

用同样的方法得到转向环的输入。

平衡小车一般由速度环,直立环,转向环组成。

部分参考代码

#include "stm32f10x.h"                  // Device header
#include "action.h"
#include "motor.h"
#include "Encoder.h"
#include "mpuiic.h"
#include "mpu6050.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
//pitch前负后正
extern float pitch,roll,yaw;
extern short gx,gy,gz,aacx,aacy,aacz;
int errtext;//测试用


 float Med_Angle=0;        // 机械中值,能使得小车真正平衡住的角度 
 float Target_Speed=0;	    // 期望速度
 
 float Vertical_Kp=400;//-450*0.6;      // 直立环Kp(平衡 震荡)
 float Vertical_Kd= 0;//-2*0.6;       // 直立环(平衡——>高频震荡)
 
 float Velocity_Kp=0;    //-680;         // 速度环Kp(小范围移动)
 float Velocity_Ki=0;   //-680/200;      // 速度环Ki
 
 float Turn_Kp=0; //2.7;//转向环kp
 
 
 
 int Vertical_out,Velocity_out,Turn_out; // 直立环&速度环&转向环的输出变量
 int Encoder_Left,Encoder_Right;
 
 int MOTO1,MOTO2;




void action(void)
{
	int PWM_OUT;
			mpu_dmp_get_data(&pitch,&roll,&yaw);//得到陀螺仪值(原始值)
			MPU_Get_Gyroscope(&gx,&gy,&gz);//得到加速度值(原始值)
			MPU_Get_Accelerometer(&aacx,&aacy,&aacz);	//得到加速度值(带符号)
	

	
	/**********转动相关角度,哪个变化最大填哪个**********/
			
			Vertical_out = zhili_pwm(pitch,Med_Angle,gy);//直立环
	
			Velocity_out = zhuanxiang_pwm(Encoder_Left,Encoder_Right,Target_Speed);//速度环
			
			Turn_out=Turn_pwm(gz);//转向环
	
			PWM_OUT = Vertical_out-Velocity_out;
			MOTO1 = PWM_OUT + Turn_out;
			MOTO2 = PWM_OUT - Turn_out;
			Limit(&MOTO1,&MOTO2);
			printf("%d,%d\r\n",MOTO1,MOTO2);
			Load_Motor_PWM(MOTO1,MOTO2);	
		
}

/*
作用:直立环
作者:Cady
最后更改日期:2024.1.12
*/

// * 直立环:
// * 公式:直立环输出=Kp x 角度偏差 +Kd x 角度偏差的微分
// Vertical_out = Vertical(pitch,Med_Angle,gy);//俯仰角
int zhili_pwm(float Angle,float Expect,float Gyro_Y)//(真实角度(俯仰角),期望角度,得到陀螺仪值(原始值))
{
  float Bias;
	int PWM_out;
  Bias=Angle-Expect; 					//(角度偏差=真实角度-期望角度)
  PWM_out=Vertical_Kp*Bias + Vertical_Kd*Gyro_Y;	//计算直立环的PWM
	return PWM_out; 							
}


// * 速度环:
// * 公式:Kp*Ek+Ki*Ek_S(Ek:电机速度偏差(真实速度-期望速度)(真实速度:左电机速度+右电机速度;期望速度:0)Ek_S:电机速度偏差的积分)
//速度环输出=Kp2*(反馈编码器值-期望编码器值)+Ki*编码器偏差的积分
//low_out_last用于保存上一次滤波后的结果
//Velocity_out = zhuanxiang_pwm(Encoder_Left,Encoder_Right,Target_Speed);
int zhuanxiang_pwm(int encoder_left,int encoder_right,int encoder_expect)//(左电机的编码器数,右电机的编码器数,期望速度)
{
	static int encoder_err,low_out,low_out_last,encoder_err_sum;//,encoder_err_sum;
	float a = 0.658;//滤波器的衰减因子
	int PWM_out;
	//1.计算速度偏差
	encoder_err = (encoder_left+encoder_right)-encoder_expect;//(反馈编码器值-期望编码器值)脉冲数
	//2.对速度偏差进行低通滤波
	//公式:low_out=(1-a)*Ek+a*low_out_last;
	low_out = (1-a)*encoder_err+a*low_out_last;
	low_out_last = low_out;
	//3.对速度偏差积分,积分出位移,速度的积分是位移
	encoder_err_sum += low_out_last;
	//4.对积分进行限幅
	if(encoder_err_sum>10000)
		encoder_err_sum = 10000;
	if(encoder_err_sum<-10000)
		encoder_err_sum = -10000;
	//5.进行最后的计算
	PWM_out = Velocity_Kp*low_out+Velocity_Ki*encoder_err_sum;
	return PWM_out;
}

/*********************
转向环:系数*Z轴角速度
*********************/
int Turn_pwm(int gyro_z)
{
	int PWM_out;
	PWM_out = (Turn_Kp)*gyro_z;
	return PWM_out; 
}





void TIM1_UP_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
    {
			action();
//			printf("pitch:%2f,roll=%2f,yaw=%2f\r\n",pitch,roll,yaw);
//			printf("gx:%d,gy=%d,gz=%d\r\n",gx,gy,gz);
			
        TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    }
}

直线行使小车(电赛)

请移步到我的主页查看

5、四种常见的通信方式

具体介绍请电击链接前往我的博客

USART串口通信

i2c通信

SPI通信

CAN通信

 6、常见的外设及与单片机的连接

红外循迹 

红外循迹模块是开关量,只有0和1这两种状态,具有更新速度快的特点

灰度传感器 

灰度传感器相对红外循迹来说,提高了环境适应能力。

灰度传感器有数字量、模拟量、偏移量三种。

数字量即为开关量,模拟量需要使用ADC通道读取,然后用DMA数据转运

偏移量一般由串口发送,适用于高速运动的小车配合模糊pid使用,不适合用于单纯的pid控制,调节过程太难控制了。

温湿度传感器

角速度传感器

AI离线语音模块

CO传感器

超声波

热敏电阻、光敏电阻

openmv

树莓派(opencv)

7、指针在单片机上的运用 

8、常用的自动控制方法(自动控制原理)

【C51 --- 单片机学习历程与分享】 C51 --- 学习历程记录与分享Vlog---001 51单片机开篇介绍 阅读详情

相关推荐

FPGA、OpenCV和单片机怎么选

单论行业前景的情况下优先级如下: 首选FPGA; 其次是OpenCV; 最后是51和STM32; 下面是自己对这三个行业的一点见解。 1.FPGA 学习难易:FPGA的学习成本非常高,FPGA没有什么封装好的片内资源给你用,都是你需要什么就写什么,而且对于使用者的英文学习程度有一定的要求;FPGA很少有靠谱的资料和教程,市面上做FPGA教程的,拿来入门还可以,但是做到开拓性的工作基本上只有官方的文档和论文能查询,而且学习的时间成本很高,单片机编译烧录基本上在几分钟之内就可以完成,但是FPGA上稍微

华维单片机编程 1243

STM32 带位操作与LED直接用PAOUT(x),PBOUT(x)输出高低电平

STM32,位操作,led,跑马灯,

yuezhaoxu001的博客 6840

学习单片机:从入门到精通

通过使用开发板,我们可以方便地学习和实践单片机的各种功能和特性。本文介绍了单片机的基本概念和使用方法,包括单片机的介绍、开发板的功能介绍、开发板的使用方法,以及附带了一个简单的示例代码。学习单片机是电子工程师和爱好者的重要一步,本文将介绍学习单片机的方法以及单片机的基本概念和使用。扩展功能:在掌握基本的单片机编程和调试技巧后,可以尝试使用开发板上的其他外设和接口,探索更多的功能和应用。开发板是一种带有单片机芯片和丰富外设的电路板,提供了丰富的接口和功能,方便我们学习和测试单片机的各种特性。

UoEmacs_Lisp的博客 893

【嵌入式C语言】位带操作

位带操作实现了通过普通的加载/存储等指令对单一的1bit进行读写的功能。也就是说把1bit膨胀为32bit的字来操作(位带操作就是用一个地址代表一个比特,正常来说一个地址代表的是32bit,一次性操作的也是32bit)。 比如: #define LED0 PBout(1) //其中 PBout(1)是位带操作 我们直接 LED0 = 1; 通过这个位带操作直接将IO口PB1置为了高电平,也就是通过位带操作直接改变了某一bit的值。 而普通的操作比如 int a = 1; 是将a所代表

仅以此博客记录日常学习工作中所思所得,水平有限,难登大雅,万望海涵。 1万+

嵌入式学习大目录——MCU/RTOS/单片机/Linux/机器人

2301_79143213的博客 562

CentOS7下用FreeRADIUS+AD实现802.1X认证完整指南(附华为交换机配置)

本文提供了在CentOS 7系统上部署FreeRADIUS并与Windows Active Directory集成,以实现企业级802.1X网络认证的完整实战指南。内容涵盖从架构原理、服务器配置(包括Kerberos、Samba Winbind集成)、FreeRADIUS核心设置与AD联动,到华为交换机的详细配置步骤。重点解决了生产环境中常见的高可用、动态VLAN下发及深度排错问题,为企业构建集中、安全的网络准入控制体系提供了经过验证的解决方案。

weixin_29273849的博客 407

单片机学习难度大吗

感觉这是很老的编程了,要不要学还是直接学最新~

suyuanhua的博客 98

一。CST 电路 微带低通滤波器设计练习 I

CST 电路和系统软件功能还是比较强的,我们一起一步一步学习,一起进步。 这个例子出现在CST的电路和系统软件功能(Circuit Simulation & SAM (System Assembly and Modeling) )介绍资料中。本文是用CST2021建模操作仿真到,不同版本在视觉和结果上稍有不同。 利用电路模块可以很快很准确地建立3D微带平面电路,用于仿真滤波器、功分器、巴伦等等。

麦克斯韦信徒的博客 3237

作为一个27岁的人,学习单片机然后进入这行的可能性大吗?

但是在学习过程中最好多做项目。网上有很多成熟的开发板,买一个,先照着做一部分。最后尝试抛开你现在单片机的代码,去网上找近似的代码移植到你现在的开发板上,对照datasheet进行,调不通的话再对照开发板本身自带的代码,如此多进行几次,就可以一边做项目,一边找工作了。掌握算法的思想,可以借鉴用于其他场景,比如架构设计和方案设计。掌握了算法设计的思想,很多难题都迎刃而解了,也将成为你竞争力的体现。作为一个27岁的人,学习单片机然后进入这行的可能性大吗?作为一个27岁的人,学习单片机然后进入这行的可能性大吗?

linlaoshi2009的博客 487

告别手动打点计时:用Lauterbach TRACE32的RunTime功能,5分钟搞定嵌入式代码段性能分析

本文深入解析了Lauterbach TRACE32的RunTime功能在嵌入式代码性能分析中的革命性应用。通过硬件级计时原理和非侵入式设计,该功能能快速精确测量代码执行时间,显著提升开发效率,特别适用于汽车电子和工业控制领域。文章还提供了从基础设置到高级分析的完整实战指南,帮助工程师轻松掌握这一强大工具。

weixin_30615767的博客 279

UIPath 获取网页元素做遍历点击的实现

UIPath 获取网页元素做遍历点击的实现

geekqian的博客 1万+

蓝桥杯单片机学习笔记(十一)—— 调度器大模板构建

本文介绍了一个用于蓝桥杯单片机比赛的集成化调度器模板,整合了LED、数码管、按键、传感器等外设驱动。模板采用调度器架构实现多任务协同运行,避免了传统延时变量方法的问题。详细讲解了各模块的初始化、LED控制(含蜂鸣器/继电器/电机)、数码管显示(含小数点处理)、按键扫描(支持矩阵按键和双按键识别)等核心功能的实现方法,并提供了优化建议如20ms刷新间隔、NE555测频注意事项等。所有代码均配有详细注释,适合初学者学习参考。

GOAT210的博客 900

非华为电脑也能用上鸿蒙生态?手把手教你给Win10/Win11装上最新华为电脑管家(附移动应用引擎开启方法)

本文详细介绍了如何在非华为品牌的Windows 10/11电脑上安装华为电脑管家并启用移动应用引擎,实现鸿蒙生态的无缝协同体验。从BIOS设置到安装步骤,再到性能优化和常见问题解决,手把手教你解锁跨设备高效工作流。

weixin_42542669的博客 4241

单片机学习笔记————指针的第二大好处,指针作为数组在函数中的输入接口

proteus虚拟串口的实现:https://mp.youkuaiyun.com/console/editor/html/107251649 一、使用proteus绘制简单的电路图,用于后续仿真 二、编写程序 /******************************************************************************************************************** ---- @Project: Pointer ---- @F.

DJDN426611的博客 456

Oracle Linux中/var/log/pcp/pmlogger目录膨胀问题分析与解决

本文详细分析了Oracle Linux系统中/var/log/pcp/pmlogger目录异常膨胀的常见原因与解决方案。当PCP性能监控框架的pmlogger服务日志轮转或清理机制失效时,会导致该目录无限增长,威胁系统稳定。文章提供了从服务状态诊断、配置文件审查到安全清理与预防策略的全流程实战指南,帮助运维人员快速回收磁盘空间并恢复服务正常运行。

weixin_29208327的博客 372

YOLOv11改进有效涨点专栏目录 | 含卷积、主干、注意力机制、Neck、检测头、损失函数、二次创新C2PSA/C3k2等各种网络结构改进

Hello,各位读者们好.本文为最新YOLOv11有效涨点专栏目录,YOLOv11以及发布了一个月左右,这个过程中我也是给大家整理了许多的机制,其中包含了C3k2、主干(均支持根据yolov11训练的版本进行二次缩放,全系列都能轻量化)、检测头、注意力机制、Neck多种结构上创新,也有损失函数和一些细节点上的创新。

Snu77的博客 8万+

学习笔记-单片机蓝桥杯大模板更新-米醋

必须要用同一个全局变量:不然Beep,Motor,Relay任意两者同时使用时,会数据覆盖,按键时间越长,消抖效果越好,但按键生效越晚(按键的反应精度不够),界面切换时间不达标。第五位:0自动递增,先给个0号,下一刻读取1号,下一刻读取2号,我们要手动,所以0。AIN0外部输入,AIN1光敏电阻,AIN2差分输入(没考过),AIN3滑动变阻器。如果有多个ROM,就会检查,但板子上只有一个ROM,检查需要花时间,所以跳过。如果自己的代码写了putchar,不会用系统的putchar,而是用自己的。

2301_81831368的博客 693

MinerU 本地部署完整指南:从零构建你的私有文档智能处理系统

摘要: MinerU是一款开源本地化文档解析工具,可将PDF、Word等文件智能转换为结构化Markdown,保留表格、公式、图片等元素。其核心优势包括: 隐私安全:完全离线处理,避免云端数据泄露风险; 高精度解析:基于深度学习还原文档逻辑结构,支持多格式输入; 易集成:提供RESTful API,适合金融、医疗等敏感场景。部署需Python 3.9+环境,支持CPU/GPU运行,推荐配置16GB内存及NVIDIA显卡。通过命令行或Docker即可快速启动,适用于知识库构建、RAG系统预处理等。

reset2021的博客 752

嵌入式学习路径大PK:按部就班 vs 直接上手单片机,哪种更适合你?

两种路径各有优劣,选对了能事半功倍,选错了可能半途而废。本文将结合大学生实际场景,对比两种路径的适用人群,并给出“折中方案”,帮你找到最适合自己的学习节奏。C语言(3-6个月)→ 数字电路基础(1-2个月)→ 单片机(6个月+)单片机开发板(如51/STM32)→ 边做项目边补C语言和数电知识。:系统性补全C语言、数电、操作系统等核心知识。:用最短时间做出第一个单片机项目,激发兴趣。——给大学生的“避坑指南”与“加速攻略”:根据兴趣选择方向,深入学习。

学无止境,止于至善 890

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

stm32栈应该多大_STM32单片机学起来没那么难,老司机告诉你怎么学习!

单片机用处这么广,尤其是STM32,这么火!如何快速上手学习呢?“不要去学STM32”。我不是说STM32不好,而是这种为了学习单片机而去学习单片机的思路不对。你问,如何系统地入门学习stm32?这本身就是一个错误的问题。假如你会使用8051 , 会写C语言,那么STM32本身并不需要刻意的学习。你要考虑的是, 我可以用STM32实现什么?为什么使用STM32而不是8051? 是因为51的频率太低...

weixin_39603622的博客 126

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

上海985大一女大第一次学习STM32单片机

我们用的是180度舵机,就是0-180度的舵机给它一个特定的PWM信号舵机以一个特定的速度转动,方向可以控制但不能转动到特定的角度,闭环控制,速度控制稳定,需要一个20ms50HZ左右的时基脉冲,一般是0.5ms-2.5ms总间隔时这个2ms。我现在还要处理的是整个底盘的那个代码,还有那个云台到底是什么东西,还有那个pid怎么写,hal库怎么弄,亚克力板我怎么解决,我应该是先写一个标准库的代码吧,以及买的那个马达电机那个资料课程我还没有弄清楚,以及那个硬件连接。不是黑线,返回高电平,有反射。

Ahottie的博客 258

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

大一下这个时候才开始自学单片机,晚吗?

以下是一些理由和建议,说明为什么这个时候开始自学单片机是合适的,并且提供一些学习的指导:理由。基础扎实:大一的课程通常涵盖了基本的电路、电子学和编程基础,这些都是学习单片机的前提条件。选择合适的单片机:可以从常见的 51 单片机(如 STC 系列)入手,或者选择更现代化的 STM32 系列,后者功能更强大,应用更广泛。大一下学期开始自学单片机是一个很好的起点,通过系统的学习和实践,你可以逐步掌握这门技术,并在未来的学习和职业生涯中受益。充分利用各种学习资源,积极参与实践项目,你一定能取得显著的进步。

T1246632660的博客 359

2024年有关嵌入式、单片机、51单片机、STM32、的一些概念详解,2024年最新物联网嵌入式开发者升职加薪的8项技能

根据历史的、本质性的、普遍性的要求,嵌入式系统应该定义为:“嵌入在对象系统中的专用计算机系统”。嵌入式、专用式和计算机系统是构成嵌入式系统三大基本元素。对像系统是指嵌入嵌入式系统的主机系统。可裁剪性。支持开放性和可伸缩性的体系结构。强实时性。EOS实时性一般较强,可用于各种设备控制中。统一的接口。提供设备统一的驱动接口。操作方便、简单、提供友好的图形GUI和图形界面,追求易学易用。强稳定性,弱交互性。嵌入系统在运行过程中不需要太多用户干预,而 EOS系统要对其进行管理,具有较强的稳定性。

2401_85014148的博客 1593

单片机学习笔记————指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数

proteus虚拟串口的实现:https://mp.youkuaiyun.com/console/editor/html/107251649 一、使用proteus绘制简单的电路图,用于后续仿真 二、编写程序 /******************************************************************************************************************** ---- @Project: Pointer ---- @F.

DJDN426611的博客 475

51单片机学习笔记之单片机及开发板介绍

介绍51单片机结构等

Dg24682538000的博客 888

单片机与PLC的详细比较

要搞清楚单片机与PLC的异同,首先得明确什幺是单片机,什幺是PLC。对此,我们简要回顾一下计算机的发展历程也许有帮助,按计算机专家的原始定义, 计算机系统由五大部分--即控制单元(CU)、算术运算单元(ALU)、存储器(Memory)、输入设备(Input)、输出设备(Output)组 成。 早期计算机(晶体管的或集成电路的,不包括电子管的)的CU或ALU由一块甚至多块电路板组成,CU和ALU是分离的,随着集成度的提高,CU和 ALU合在一块就组成了中央处理单元(CPU),接着将CPU集成到单块集成电

理论结合实践,开启你的电子学习新方案! 1万+
上一篇: YOLOv5lite+onnxruntime树莓派实时识别检测
下一篇: 四种基本通信方式
是小陈同学吖~
博客等级 码龄4年 156粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值