基于STM32的PID算法控制电机调速

一、制作目标

     以STM32F103C8T6单片机作为主控,使用PID控制算法,控制TB6612FNG电机驱动板模块驱动直流减速电机(带AB相编码器),实现任意设定转速的电机转速动态控制,类似于汽车的定速巡航功能,可将速度设定在固定转速,同时,设置5个按键,分别实现电机的正转、反转、停止、加速和减速控制。

二、做好的实物结果

三、模块清单

1、STM32F103C8T6单片机

2、18650电池2节3.7V串联

3、OLED屏

4、TB6612FNG电机驱动板

商品详情

5、AB相编码器直流减速电机

https://item.taobao.com/item.htm?_u=p8bd2jv1548&id=637752100260&pisk=g2HY6st-IUYcO6HYqmRlIv5hbp-oBQmq4qoCIP4c143-WDImi54g1R3EJ56Mg-VTB4aonrm0hlNsS2G0lj4GBA3rktXghVvT5DlybrqmnGEsNqBiim4mwGeqEjXgoEytf229-evHKmo4LRTH-iyhVywQAi11Srs7VRqtZm1cAmo4QPsl5BcS0NQOK5__fPt8FlEO5RNb1z97zl411VabNaZLYRws5rw5NlqAhoaj5_a7Ak5bfSwsV7ZLfRas5Rt-VzrTCPG4AIUMryWtOd_5oitIEO6seoFWZmaWEcHuD8UKcY1PahrYMyibyexp8APSb5HNbOFncXuaAq_XWPk-VxGIRUf0lXEtxf3pdOrrGqH76YxwK0yYXWw_wG6s2-a_9Y2BdaErNmcs3qIOCokmK5UUwh6as8M3OXgAbHo7hlgaTAYF3risYvl3pKQUDbiLegRmKvEXPwDSvstJ215aGu7-QabxYr_uYuUHq3fN_73Y2yxJ215aGur8-3uF_1P-k&spm=a1z09.2.0.0.3edc2e8dsKV9iY&sku_properties=-3%3A-3

 四、电路原理图

五、程序组成

1、main.c中

#include "bsp_sys.h"
#include "stdio.h"
#include "key.h"extern int setpoint;
extern u8 OLED_ADC_Flag;
extern u8 OLED_Speed_Flag;extern u16 ADC_BAT_Val,ADC_BAT_temp;
extern u16 ADC_SYS_Val,ADC_SYS_temp;
extern int Encoder_B,Encoder_A;float adc11;
float adc22;
char adc1[5];
char adc2[4];uint8_t key;
uint8_t count;int Speed_B,Speed_A;
char Speed_B1[5];
char Speed_A1[5];char state=0;/*** @brief  主函数* @param  无  * @retval 无*/
int main(void)
{	
//***************************设置局部变量***************************////*****************************************************************////*****************************系统初始*****************************//STM32_System_Init();//所有系统配置在这个函数里完成
//	KEY_Init();//*****************************************************************//
//Car_Back();Car_Stop();OLED_ShowString(32,4,"stop    ",12);while (1){key=KEY_Scan(0);if(key==1){setpoint=setpoint+20;//设定速度加20}if(key==2){setpoint=setpoint-20;//设定速度减20}
//		OLED_ShowNum(16,3,setpoint,3,12); OLED_ShowNum(32,2,setpoint,3,12); //显示设置的速度if(key==3){state=1;OLED_ShowString(32,4,"forword ",12);//显示屏显示正转
//			Car_Go();}if(key==4){state=2;OLED_ShowString(32,4,"reversal",12);//显示屏显示反转
//			Car_Back();}if(state==1){Car_Go();}//控制电机正转if(state==2){Car_Back();}//控制电机反转if(OLED_ADC_Flag==1)//电压1S显示一次{OLED_ADC_Flag=0;adc11=ADC_BAT_Val*3.3/4096*11;sprintf(adc1,"%2.1f",adc11);
//		  OLED_ShowString(16,0, adc1,12);  //OLED显示电池电压if(adc11<7.4){OLED_ShowString(0,0,"Low Power",12);//如果电池低于7.4V,提示低电压,需要充电}adc22=ADC_SYS_Val*3.3/4096*2;sprintf(adc2,"%2.1f",adc22);
//		  OLED_ShowString(96,0, adc2,12);  //OLED显示电池电压//			OLED_ShowNum(16,2,setpoint,3,12); OLED_ShowNum(64,1,Speed_A,3,12); //显示电机当前的实际速度}}}	

2、TB6612.C中

#include "tb6612.h"//***************************TB6612配置***************************//
void TB6612_Init(void)
{TB6612_PWM_Init(3600, 1);//PWM频率初始化20KHzTB6612_GPIO_Config();    //电机驱动IO配置
}//***************************PWM频率及占空比初始化***************************//
//=====初始化PWM 20KHZ 高频可以防止电机低频时的尖叫声
// ARR= 3599 时频率为20Khz
//PB0控制PWMA--left motor,PB1控制PWMB--right motor。STBY直接拉高
//arr:自动重装寄存器,psc分频系数
//PWM的频率 = 72MHz/ARR/PCS 例如  20K = 72M/3600/1 =  20K
void TB6612_PWM_Init(u32 arr, int psc)
{TIM_OCInitTypeDef TIM_OCInitSructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;GPIO_InitTypeDef    GPIO_InitStructure;//配置pwm输出端口RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_8| GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		         // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化定时器RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);TIM_TimeBaseStructure.TIM_Period = arr-1;                   //自动重新装载寄存器周期的值澹ㄥ计数值澹)TIM_TimeBaseStructure.TIM_Prescaler = psc-1;                  //时钟分频系数TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //对外部时钟进行采样的时钟分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure);              //参数初始化TIM_ClearFlag(TIM4, TIM_FLAG_Update);TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);//设置通道3 pwm参数TIM_OCInitSructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitSructure.TIM_OutputState= TIM_OutputState_Enable;TIM_OCInitSructure.TIM_Pulse = 0;                            //占空比=TIM_OCInitSructure.TIM_OCPolarity = TIM_OCPolarity_High;     //当定时器计数值小于CCR1_Val时为高电平TIM_OC3Init(TIM4, &TIM_OCInitSructure);                      //参数初始化TIM_OC3PolarityConfig(TIM4, TIM_OCPreload_Enable);           //开始输出pwm//设置通道4 pwm参数TIM_OCInitSructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitSructure.TIM_OutputState= TIM_OutputState_Enable;TIM_OCInitSructure.TIM_Pulse = 0;                            //占空比= TIM_OCInitSructure.TIM_OCPolarity = TIM_OCPolarity_High;     //当定时器计数值小于CCR1_Val时为高电平TIM_OC4Init(TIM4, &TIM_OCInitSructure);                      //参数初始化TIM_OC4PolarityConfig(TIM4, TIM_OCPreload_Enable);           //开始输出pwmTIM_ARRPreloadConfig(TIM4, ENABLE);                          //启动自动重装TIM_Cmd(TIM4, ENABLE);                                       //启动定时	
}//***************************占空比调节***************************//
//占空比 = TIMx_CCRx / TIMx_ARR
//moto_r:右轮电机,moto_l:左轮电机.   数值 0-3600
void TB6612_PWM_Out(u16 moto_l, u16 moto_r)
{TIM_OCInitTypeDef TIM_OCInitSructure;TIM_OCInitSructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitSructure.TIM_OutputState= TIM_OutputState_Enable;//CH3 左电机TIM_OCInitSructure.TIM_Pulse = moto_l;               //占空比= ccr/3600TIM_OC3Init(TIM4, &TIM_OCInitSructure);              //参数初始化TIM_OC3PolarityConfig(TIM4, TIM_OCPreload_Enable);   //开始输出pwm//CH4 右电机TIM_OCInitSructure.TIM_Pulse = moto_r;               //占空比= ccr /3600TIM_OC4Init(TIM4, &TIM_OCInitSructure);              //参数初始化TIM_OC4PolarityConfig(TIM4, TIM_OCPreload_Enable);   //开始输出pwmTIM_ARRPreloadConfig(TIM4, ENABLE);                  //启动自动重装}	void TB6612_GPIO_Config(void)
{		/*定义一个GPIO_InitTypeDef类型的结构体*/GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);                                	  /*开启GPIO的外设时钟*/																   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;	    /*选择要控制的GPIO引脚*/	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                                          /*设置引脚模式为通用推挽输出*/   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                         /*设置引脚速率为50MHz */ GPIO_Init(GPIOB, &GPIO_InitStructure);                                                 /*调用库函数,初始化GPIO*/	
}//以下单独定义每个轮子运动状态,如果不对可以修改里面的参数
//比如左轮前进,AIN1(0),AIN2(1).如果往后转了,就改成AIN1(1),AIN2(0)void Motor_Left(u8 state)
{if(state == GO)      //左电机前进{AIN1(0);AIN2(1);}if(state == BACK)   //左电机后退{AIN1(1);AIN2(0);}if(state == STOP)  //停转{AIN1(1);AIN2(1);}
}void Motor_Right(u8 state)
{if(state == GO)      //右电机前进{BIN1(0);BIN2(1);}if(state == BACK)   //右电机后退{BIN1(1);BIN2(0);}if(state == STOP)  //停转{BIN1(1);BIN2(1);}
}//以下定义了小车整体的运动方向。转弯靠左右电机差速实现。void Car_Go(void)
{//小车前进//左电机前进      右电机前进Motor_Left(GO);   Motor_Right(GO);
}void Car_Back(void)
{//小车后退//左电机后退     右电机后退Motor_Left(BACK);   Motor_Right(BACK);}void Car_Right(void)
{//小车右转圈//左电机前进      右电机后退Motor_Left(GO);   Motor_Right(BACK);
}void Car_Left(void)
{//小车左转圈//左电机后退      右电机前进Motor_Left(BACK);   Motor_Right(GO);
}void Car_Stop(void)
{//小车停车//左电机停止      右电机停止Motor_Left(STOP);   Motor_Right(STOP);
}

3、car.c中

 #include "car.h"
#include "outputdata.h"
#include "PID.h"u8 temp1=0;
u8 OLED_Timer1=0;//时间片,更新ADC
u8 OLED_Timer2=0;//时间片,更新速度
u8 Encoder_Timer = 0;u8 OLED_ADC_Flag=0;
u8 OLED_Speed_Flag=0;u16 ADC_BAT_Val,ADC_BAT_temp;
u16 ADC_SYS_Val,ADC_SYS_temp;int Encoder_B,Encoder_A;
extern float OutData[4];
extern int Speed_A,Speed_B;uint16_t jsb = 1320;//11x30x4,电机的输出轴转一圈产生的脉冲数量。11是电机霍尔转动一圈的脉冲数量,30是减速比,4是四倍频
//设置目标转速
int setpoint = 60;//RPMint Moto_A,Moto_B;
//目标转速转换成脉冲数量,因为最后PID控制的是脉冲数量
#define Set_Moto_A  setpoint*jsb/1200
#define Set_Moto_B  setpoint*jsb/1200
//Time1定时器1中断服务函数
//10ms定时
void TIM1_UP_IRQHandler(void)
{if(TIM_GetFlagStatus(TIM1, TIM_IT_Update) != RESET)   //时间到了{TIM_ClearITPendingBit(TIM1, TIM_FLAG_Update);//清中断LED_Flash(50);//500ms闪烁一次temp1++;OLED_Timer1++;OLED_Timer2++;Encoder_Timer++;if(Encoder_Timer>=5)//电机转速50ms采样一次{Encoder_Timer = 0;Encoder_A=Read_Encoder(2)-30000;                        //读取编码器,计算出变化量Encoder_B=-((Read_Encoder(3)-30000));                   //读取编码器,计算出变化量,负号是因为两个电机 相对位置180°if(Encoder_A < 0){Encoder_A = -Encoder_A;}if(Encoder_B < 0){Encoder_B = -Encoder_B;}//PID计算Moto_B = PID_VelocityPidB(Set_Moto_B, Encoder_B);Moto_A = PID_VelocityPidA(Set_Moto_A, Encoder_A);//PWM占空比给定时器,控制电机TIM4->CCR3=Moto_B;  //更新pwmTIM4->CCR4=Moto_A; //更新pwm//编码器脉冲数转换成圈数 单位RPMSpeed_A=Encoder_A*1200/1320;Speed_B=Encoder_B*1200/1320;//虚拟示波器显示OutData[0] = Speed_A;OutData[1] = Speed_B;OutPut_Data();//打印数据,用虚拟示波器查看}//更新电压if(temp1>=10)//100ms{temp1=0;ADC_BAT_temp += Get_BAT_ADC();//ADC 采样 电池电压ADC_SYS_temp += Get_SYS_ADC();//aDC 采样 系统电压ADC_BAT_Val = ADC_BAT_temp;ADC_SYS_Val = ADC_SYS_temp;ADC_BAT_temp = 0;ADC_SYS_temp = 0;}//OLED显示更新if(OLED_Timer1 >= 100)//1S 更新一次ADC{OLED_Timer1=0;OLED_ADC_Flag = 1;}}}

4、PID.C中

#include "PID.h"//PID参数
float MotorA_kp=200;  
float MotorA_ki=2;    
float MotorA_kd=0;  float MotorB_kp=200;  
float MotorB_ki=2;    
float MotorB_kd=0; #define PID_SCALE  0.01f  //PID缩放系数int16_t PID_VelocityPidA(float Spd_Target, int16_t Spd_Now)
{static float Motor_Pwm_Out;static float bias,bias_last,bias_integral = 0;float para;bias = Spd_Target - Spd_Now;bias_integral += bias;para = MotorA_kp*bias*PID_SCALE + MotorA_kd*(bias-bias_last)*PID_SCALE + MotorA_ki*bias_integral*PID_SCALE;if(para<-1 || para>1){Motor_Pwm_Out +=para; }if(Motor_Pwm_Out > 3500) Motor_Pwm_Out = 3500;if(Motor_Pwm_Out < 0)Motor_Pwm_Out = 0;bias_last = bias;	return Motor_Pwm_Out;
}	int16_t PID_VelocityPidB(float Spd_Target, int16_t Spd_Now)
{static float Motor_Pwm_Out;static float bias,bias_last,bias_integral = 0;float para;bias = Spd_Target - Spd_Now;bias_integral += bias;para = MotorB_kp*bias*PID_SCALE + MotorB_kd*(bias-bias_last)*PID_SCALE + MotorB_ki*bias_integral*PID_SCALE;if(para<-1 || para>1){Motor_Pwm_Out +=para; }if(Motor_Pwm_Out > 3500) Motor_Pwm_Out = 3500;if(Motor_Pwm_Out < 0)Motor_Pwm_Out = 0;bias_last = bias;	return Motor_Pwm_Out;
}	

5、key.c中

#include "key.h"
#include "delay.h"//按键初始化函数 
//PA15和PC5 设置成输入
void KEY_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA,PORTC时钟GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_1;//PC15//	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;; //设置成上拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;; //设置成上拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOC15GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2;//PC14GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;; //设置成上拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOC14GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;//PC13GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;; //设置成上拉输入	  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOC13} 
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0按下
//KEY1_PRES,KEY1按下
//WKUP_PRES,WK_UP按下 
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{	 static u8 key_up=1;//按键按松开标志if(mode)key_up=1;  //支持连按		  if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==0)){Delay_ms(10);//去抖动 key_up=0;if(KEY0==0)return KEY0_PRES;else if(KEY1==0)return KEY1_PRES;else if(KEY2==0)return KEY2_PRES; else if(KEY3==0)return KEY3_PRES; }else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==1)key_up=1; 	     return 0;// 无按键按下
}

6、Encoder.c中

#include "Encoder.h"//***************************定时器2初始化 ,使用编码器功能***************************//
//左电机编码器计数
//PA15----接 编码器A相 或者电机驱动的B1A标识
//PB3 ----接 编码器B相 或者电机驱动的B1B标识
void Encoder_Init_TIM2(void)
{GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;   //GPIO功能时钟使能RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);     //禁用JTAGGPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);//配置IO口为复用功能-定时器通道GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度100MHzGPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_15;GPIO_Init(GPIOA, &GPIO_InitStructure);//TIM时钟使能RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//Timer configuration in Encoder mode TIM_DeInit(TIM2);TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);TIM_TimeBaseStructure.TIM_Prescaler = 0x0;  // No prescaling 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);TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);TIM_ICStructInit(&TIM_ICInitStructure);TIM_ICInitStructure.TIM_ICFilter = 0;TIM_ICInit(TIM2, &TIM_ICInitStructure);//Reset counterTIM2->CNT = 0;TIM_Cmd(TIM2, ENABLE);   
}//***************************定时器3初始化 ,使用编码器功能***************************//
//左电机编码器计数
//PB4----接 编码器A相 或者电机驱动的B2A标识
//PB5----接 编码器B相 或者电机驱动的B2B标识
void Encoder_Init_TIM3(void)
{GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;   //GPIO功能时钟使能RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);//配置IO口为复用功能-定时器通道GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度100MHzGPIO_Init(GPIOB, &GPIO_InitStructure);//TIM时钟使能RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 , ENABLE); //这个就是重映射功能函数//Timer configuration in Encoder mode TIM_DeInit(TIM3);TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);TIM_TimeBaseStructure.TIM_Prescaler = 0x0;  // No prescaling 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);TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);TIM_ICStructInit(&TIM_ICInitStructure);TIM_ICInitStructure.TIM_ICFilter = 0;TIM_ICInit(TIM3, &TIM_ICInitStructure);//Reset counterTIM_SetCounter(TIM3,0);TIM_Cmd(TIM3, ENABLE);  }/**************************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回  值:速度值
**************************************************************************/
int Read_Encoder(u8 TIMX)
{int Encoder_TIM;    switch(TIMX){case 2:  Encoder_TIM= (short)TIM2 -> CNT;  TIM2 -> CNT=30000;break;case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=30000;break;	//case 4:  Encoder_TIM= (short)TIM4 -> CNT;  TIM4 -> CNT=30000;break;	// case 5:  Encoder_TIM= (short)TIM5 -> CNT;  TIM5 -> CNT=30000;break;default:  Encoder_TIM=0;}return Encoder_TIM;
}

7、OLED.C中


#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "delay.h"
#include "bsp_iic.h"
//OLED的显存
//存放格式如下.
//[0]0 1 2 3 ... 127	
//[1]0 1 2 3 ... 127	
//[2]0 1 2 3 ... 127	
//[3]0 1 2 3 ... 127	
//[4]0 1 2 3 ... 127	
//[5]0 1 2 3 ... 127	
//[6]0 1 2 3 ... 127	
//[7]0 1 2 3 ... 127 			   void Write_IIC_Command(unsigned char IIC_Command)
{IIC_Start();IIC_Send_Byte(0x78);            //Slave address,SA0=0IIC_Wait_Ack();	IIC_Send_Byte(0x00);			//write commandIIC_Wait_Ack();	IIC_Send_Byte(IIC_Command); IIC_Wait_Ack();	IIC_Stop();
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{IIC_Start();IIC_Send_Byte(0x78);			//D/C#=0; R/W#=0IIC_Wait_Ack();	IIC_Send_Byte(0x40);			//write dataIIC_Wait_Ack();	IIC_Send_Byte(IIC_Data);IIC_Wait_Ack();	IIC_Stop();
}
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{if(cmd){Write_IIC_Data(dat);}else {Write_IIC_Command(dat);}}/********************************************
// fill_Picture
********************************************/
void fill_picture(unsigned char fill_Data)
{unsigned char m,n;for(m=0;m<8;m++){OLED_WR_Byte(0xb0+m,0);		//page0-page1OLED_WR_Byte(0x00,0);		//low column start addressOLED_WR_Byte(0x10,0);		//high column start addressfor(n=0;n<128;n++){OLED_WR_Byte(fill_Data,1);}}
}/***********************Delay****************************************/
void Delay_50ms(unsigned int Del_50ms)
{unsigned int m;for(;Del_50ms>0;Del_50ms--)for(m=6245;m>0;m--);
}void Delay_1ms(unsigned int Del_1ms)
{unsigned char j;while(Del_1ms--){	for(j=0;j<123;j++);}
}//坐标设置void OLED_Set_Pos(unsigned char x, unsigned char y) 
{ OLED_WR_Byte(0xb0+y,OLED_CMD);OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);OLED_WR_Byte((x&0x0f),OLED_CMD); 
}   	  
//开启OLED显示    
void OLED_Display_On(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//关闭OLED显示     
void OLED_Display_Off(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}		   			 
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  u8 i,n;		    for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); } //更新显示
}
void OLED_On(void)  
{  u8 i,n;		    for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA); } //更新显示
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 16/12 
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{      	unsigned char c=0,i=0;	c=chr-' ';//得到偏移后的值			if(x>Max_Column-1){x=0;y=y+2;}if(Char_Size ==16){OLED_Set_Pos(x,y);	for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);OLED_Set_Pos(x,y+1);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);}else {	OLED_Set_Pos(x,y);for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}
}
//m^n函数
u32 oled_pow(u8 m,u8 n)
{u32 result=1;	 while(n--)result*=m;    return result;
}				  
//显示2个数字
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//mode:模式	0,填充模式;1,叠加模式
//num:数值(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{         	u8 t,temp;u8 enshow=0;						   for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){OLED_ShowChar(x+(size2/2)*t,y,' ',size2);continue;}else enshow=1; }OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2); }
} 
//显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{unsigned char j=0;while (chr[j]!='\0'){		OLED_ShowChar(x,y,chr[j],Char_Size);x+=8;if(x>120){x=0;y+=2;}j++;}
}
//显示汉字
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{      			    u8 t,adder=0;OLED_Set_Pos(x,y);	for(t=0;t<16;t++){OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);adder+=1;}	OLED_Set_Pos(x,y+1);	for(t=0;t<16;t++){	OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);adder+=1;}					
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{ 	unsigned int j=0;unsigned char x,y;if(y1%8==0) y=y1/8;      else y=y1/8+1;for(y=y0;y<y1;y++){OLED_Set_Pos(x0,y);for(x=x0;x<x1;x++){      OLED_WR_Byte(BMP[j++],OLED_DATA);	    	}}
} //初始化SSD1306					    
void OLED_Init(void)
{ 	IIC_Init();Delay_ms(800);OLED_WR_Byte(0xAE,OLED_CMD);//--display offOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line address  OLED_WR_Byte(0xB0,OLED_CMD);//--set page addressOLED_WR_Byte(0x81,OLED_CMD); // contract controlOLED_WR_Byte(0xFF,OLED_CMD);//--128   OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverseOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 dutyOLED_WR_Byte(0xC8,OLED_CMD);//Com scan directionOLED_WR_Byte(0xD3,OLED_CMD);//-set display offsetOLED_WR_Byte(0x00,OLED_CMD);//OLED_WR_Byte(0xD5,OLED_CMD);//set osc divisionOLED_WR_Byte(0x80,OLED_CMD);//OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode offOLED_WR_Byte(0x05,OLED_CMD);//OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge PeriodOLED_WR_Byte(0xF1,OLED_CMD);//OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartionOLED_WR_Byte(0x12,OLED_CMD);//OLED_WR_Byte(0xDB,OLED_CMD);//set VcomhOLED_WR_Byte(0x30,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enableOLED_WR_Byte(0x14,OLED_CMD);//OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}  

8、bsp_sys.c中

#include "bsp_sys.h"
#include "key.h"void STM32_System_Init(void)
{	//STM32内部初始化Delay_Init(72);        //滴答时钟初始化NVIC_Config();         //中断配置配置初始化LED_GPIO_Init();     //LED初始化USART1_Init(115200);   //串口初始化IIC_Init();            //IIC模拟初始化ADC_Power_Init();KEY_Init();OLED_Init();OLED_Clear();OLED_ShowString(16,0,"PID Speed Ctr",12);
//	OLED_ShowString(0,0,"B:    V , ",12);
//	OLED_ShowString(80,0,"S:   V",12);OLED_ShowString(0,1,"Current:   RPM",12);OLED_ShowString(0,2,"Set:   RPM",12);OLED_ShowString(0,3,"Direction:       ",12);TB6612_Init();//电机驱动Encoder_Init_TIM2();TIM2 -> CNT=30000;//编码器Encoder_Init_TIM3();TIM3 -> CNT=30000;Timer1_Init();//最后启动定时器1Delay_ms(100);          //等待初始化完成
}

六、程序源码及原理图下载链接

https://download.csdn.net/download/jacklood/90532730

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/40006.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统思考—看见未来

感谢上海财经大学终身教育学院的持续邀请&#xff01;每个月&#xff0c;都会带着不同的思维火花&#xff0c;走进财大与学员们一起探索系统思考的奥秘。 这次为宜宾市的干部们带来了一场深刻的学习体验。通过系统思考&#xff0c;帮助大家从整体视角去发现问题、分析问题、解…

qwindowkit 编译教程

1、Windows编译及示例 1.1 下载源码 https://github.com/stdware/qwindowkit 1.2 cmake编译 1.3 VS构建 1.4 编译成功

HashMap的位操作是什么?HashSet 的 contains 方法复杂度是多少?红黑树简单讲一下?

一、HashMap 的位操作设计 HashMap 使用位运算优化哈希计算与索引定位&#xff0c;核心场景如下&#xff1a; 哈希扰动函数 计算键的哈希值时&#xff0c;将高16位与低16位异或&#xff1a; static final int hash(Object key) {int h;return (key null) ? 0 : (h key.hash…

软件开发过程中常用的调试工具(gdb)

gdb 因为我们公司其中脚本中有rk的gdb调试工具脚本&#xff0c;内部只需要将其打开后进行编译即可&#xff1a; 需要将编译出来的cvr_app 第一种&#xff1a;使用gdb将app给跑起来&#xff1a;gdb cvr_app 然后在出现问题时&#xff1a; 输入bt&#xff0c;可以打印出当前…

S32K144外设实验(七):FTM输出多路互补带死区PWM

文章目录 1. 概述1.1 时钟系统1.2 实验目的2. 代码的配置2.1 时钟配置2.2 FTM模块配置2.3 输出引脚配置2.4 API函数调用1. 概述 互补对的PWM输出是很重要的外设功能,尤其应用再无刷电机的控制。 1.1 时钟系统 笔者再墨迹一遍时钟的设置,因为很重要。 FTM的CPU接口时钟为SY…

Qt6相对Qt5的主要提升(AI总结)

我&#xff1a; Qt 6 相对于5 有哪些新功能&#xff1f; Qt 6 相对于 Qt 5 有诸多新功能和改进&#xff0c;以下是主要的新增特性&#xff1a; 1. 架构和核心库的重构 模块化设计&#xff1a;Qt 6 采用了更加灵活的模块化设计&#xff0c;开发者可以按需引入必要的功能模块&a…

一文解读DeepSeek的安全风险、挑战与应对策略

引言 DeepSeek作为中国领先的AI大模型提供商&#xff0c;凭借其开源、低成本和高性能的优势&#xff0c;迅速在全球AI市场占据重要地位。然而&#xff0c;随着其应用范围的扩大&#xff0c;DeepSeek在数据安全、模型漏洞、网络攻击等方面面临严峻挑战。本文基于最新公开资料&am…

文生图语义识别插件使用(controlnet)

1. 插件下载(github) https://github.com/Mikubill/sd-webui-controlnet https://github.com/lllyasviel/ControlNet2. 模型下载(hugging face) https://github.com/Mikubill/sd-webui-controlnet/wiki/Model-download https://huggingface.co/bdsqlsz/qinglong_controlnet-l…

论华为 Pura X 折叠屏性能检测

在科技浪潮中&#xff0c;折叠屏手机以其创新形态掀起市场热潮。华为 Pura X 作为华为最新折叠手机&#xff0c;承载前沿科技与精湛工艺&#xff0c;成为行业焦点。它融合先进折叠屏技术与优质材质&#xff0c;致力于打破传统手机使用边界&#xff0c;为用户开启全新体验。但产…

多路转接Poll

在之前我们讲过select是最古老的多路转接方案&#xff0c;古老就意味着他不是很方便使用&#xff0c;他需要用户手动保存fd_set这个位图结构&#xff0c;来表示读写事件的关注与否或者就绪性。 而且由于fd_set的大小是固定的&#xff0c;这就意味着他能管理的套接字文件描述符是…

C语言贪吃蛇实现

When the night gets dark,remember that the Sun is also a star. 当夜幕降临时&#xff0c;请记住太阳也是一颗星星。 ————《去月球海滩篇》 目录 文章目录 一、《贪吃蛇》游戏介绍 二、WIN32部分接口简单介绍 2.1 控制台窗口大小设置 2.2 命令行窗口的名称的变更 2…

基于深度学习的图片识别系统(下)

文章目录 前言1.任务描述2.模型搭建3.代码解释3.1模型加载3.2加载数据3.3模型权重的保存3.4学习率3.5过拟合3.6训练模型3.7调试检查 4.结果分析5. 完整代码结语 前言 书接上回&#xff0c;我们已经完成数据预处理部分的内容&#xff0c;后续仍需要对表格进行裁剪&#xff0c;此…

再学:区块链基础与合约初探 EVM与GAS机制

目录 1.区块链是什么 2.remix ​3.账户​ ​4.以太坊三种交易​ 5.EVM 6.以太坊客户端节点 ​7.Gas费用 8.区块链浏览器 1.区块链是什么 只需要检验根节点 Merkel根是否有更改&#xff0c;就不用检查每个交易是否有更改。方便很多。 2.remix 3.账户 如果交易失败的话&…

Java 中装饰者模式与策略模式在埋点系统中的应用

前言 在软件开发中&#xff0c;装饰者模式和策略模式是两种常用的设计模式&#xff0c;它们在特定的业务场景下能够发挥巨大的作用。本文将通过一个实际的埋点系统案例&#xff0c;探讨如何在 Java 中运用装饰者模式和策略模式&#xff0c;以及如何结合工厂方法模式来优化代码…

HCIP_NOTE03_网络组成

网络组成 LAN MAN WAN 园区网 企业或机构内部的网络,分大中小型 行业园:企业园网 校园网 政务园 商业园 三层交换机 数据大量交换的局域网内部,转发效率高,有简单的路由功能 路由器 进出口网络,适用于复杂的网络环境,选路需求 无线网 信号传输稳定性差---- 电磁波易受干…

简记_单片机硬件最小系统设计

以STM32为例&#xff1a; 一、电源 1.1、数字电源 IO电源&#xff1a;VDD、VSS&#xff1a;1.8~3.6V&#xff0c;常用3.3V&#xff0c;去耦电容1 x 10u N x 100n &#xff1b; 内核电源&#xff1a;内嵌的稳压器输出&#xff1a;1.2V&#xff0c;给内核、存储器、数字外设…

32.[前端开发-JavaScript基础]Day09-元素操作-window滚动-事件处理-事件委托

JavasScript事件处理 1 认识事件处理 认识事件(Event) 常见的事件列表 认识事件流 2 事件冒泡捕获 事件冒泡和事件捕获 事件捕获和冒泡的过程 3 事件对象event 事件对象 event常见的属性和方法 事件处理中的this 4 EventTarget使用 EventTarget类 5 事件委托模式 事件委托&am…

LeetCode hot 100 每日一题(15)——48.旋转图像

这是一道难度为中等的题目&#xff0c;让我们来看看题目描述&#xff1a; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 提示…

图灵300题-21~40-笔记002

图灵300题 图灵面试题视频&#xff1a;https://www.bilibili.com/video/BV17z421B7rB?spm_id_from333.788.videopod.episodes&vd_sourcebe7914db0accdc2315623a7ad0709b85&p20。 本文是学习笔记&#xff0c;如果需要面试没有时间阅读原博文&#xff0c;可以快速浏览笔…

09_从经典论文入手Seq2Seq架构

Sequence to Sequence 架构 Paper链接 Sequence to Sequence Learning with Neural Networks B站课程ShusenWang 核心思想 关键的改进点 In this paper, we show that a straightforward application of the Long Short-Term Memory (LSTM) architecture [16] can solve …