声明:文章内容来自网络总结,站在巨人肩膀上我们可以看得更远。
更新说明:更新了一些代码
本文总结了作者两年半一来的学习内容,时间关系还没完成,后续将继续更新,欢迎一起分享交流
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
DMA
DMA区别于中断系统,它拥有一个独立的运算空间,不需要消耗cpu的运算资源
3、定时器
定时器类型
stm32f103c8t6的定时器类型如下
| 类型 | 编号 | 总线 | 功能 |
| 高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
| 通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
| 基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
定时器常用于计时、中断等
4、电机控制(pid)
pwm
pwm的运用——舵机

红色接+,黑色接-,黄色/棕色接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的运用——直流减速电机

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);
}
}
编码器的使用

编码器代码参考
使用定时器的编码器模式
/**************************************************************************
* 函数功能:把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控制,调节过程太难控制了。
795

被折叠的 条评论
为什么被折叠?



