单片机学习大总结

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

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

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

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的定时器类型如下

类型

编号

总线

功能

高级定时器

TIM1TIM8

APB2

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

通用定时器

TIM2TIM3TIM4TIM5

APB1

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

基本定时器

TIM6TIM7

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、常用的自动控制方法(自动控制原理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值