stm32 小车_stm32小车 - CSDN
精华内容
参与话题
  • 其实前几周就已经将小车调好并且也发了... 这个小项目完全是我一个人搞得,因为组内的小伙伴们都还没学32,他们使用arduino搭的小车,但是毕竟实现的功能还是很简单的,也只是做了一周不到的时间。 首先是小车的...

            其实前几周就已经将小车调好并且也发了视频,但是每天忙于复习,也没有时间来对小车的流程设计、硬件设计、程序编写进行一个总结,正好周五可以休息一下,就分三个模块对这个智能小车项目进行一个总结。

            这个小项目完全是我一个人搞得,因为组内的小伙伴们都还没学32,他们使用arduino搭的小车,但是毕竟实现的功能还是很简单的,也只是做了一周不到的时间。

            首先是小车的流程设计,文老师总是教导,每做一个项目,一开始要写需求文档、画流程图,项目中要写开发文档,项目完成后要写总结。因为这次的项目是一个人搞的,所以说开发文档在制作过程中并没有写。

            需求以及所用的模块如下 

                功能要求:

                1、 走直线

                2、 寻线(S弯)

                3、 避障碍

                4、 蓝牙控制 

                模块清单:

                电机驱动L298N

                红外对管       *2

                超声波测距 HC-SR04

                电源模块 12->3.3 / 5 / 12

                减速直流电机(6V)

                蓝牙模块 HC-05

                LM2596 DC-DC稳压模块

                SG90 9克微型舵机

                //码盘测速模块

                单片机: stm32f103c8t6

            具体的模块讲解以及使用心得会在后面的硬件设计博客中记录。

           接下来是流程图

            1、避障碍

           

            2、巡线

                

            3、蓝牙控制

            


    展开全文
  • 基于stm32vet6小车程序,通信模块为ESP8266,采用手机APP控制
  • 都行西昌卫星发射中心2018年全年17战17捷!一起回顾[威武][威武]】12月25日00时53分,我国在西昌卫星发射中心用长征三号丙运载火箭,成功将通信技术试验卫星三号发射升空,卫星进入预定轨道。中国航天的“超级2018”...
  • STM小车移动篇

    千次阅读 2018-11-27 14:27:28
    上一章讲完但是还是没有让小车动起来,这章我们就可以让小车动起来 驱动模块 驱动模块我们选用的是L298N模块 ENA,ENB:是用来控制小车速度的,这个目前阶段不用管,亲测电压为4.8V。 EN1-4,是控制电机正反...

    上一章讲完但是还是没有让小车动起来,这章我们就可以让小车动起来
    在这里插入图片描述驱动模块
    驱动模块我们选用的是L298N模块
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    ENA,ENB:是用来控制小车速度的,这个目前阶段不用管,亲测电压为4.8V。
    EN1-4,是控制电机正反转的
    OUT1-4每个连接同侧一根电机线
    在这里插入图片描述接好的俯视图
    在这里插入图片描述+12v接电池座红线。
    GND一端接电池座黑线,一端接开发板GND。
    +5V接开发板5V电压
    EN1-2接PB0,PB1。
    EN3-4接PB2,PB3。
    好了连接完毕了,让我们开始写代码开始跑吧。

    
    int main(void)
    { 
     
    	delay_init(168);		  //初始化延时函数
    	LED_Init();		        //初始化奔跑函数
    	
    
    	
    	while(1)
    	{
    		GO0=0;
    		GO1=1;
    		GO2=0;
    		GO3=1;
    		delay_ms(2000);
      	GO0=1;
    		GO1=0;
    		GO2=1;
    		GO3=0;
    		delay_ms(2000);
      	GO0=0;
    		GO1=0;
    		GO2=0;
    		GO3=0;
    		delay_ms(2000);
    		//先正转后反转最后停止
    		
    	}
    }
    
    

    在这里插入图片描述
    高速旋转的小车,嘿嘿,赶紧去试试吧!
    (https://pan.baidu.com/s/135SY-Qqa1dPYJXxn2DKkcw)9ime
    免费代码链接
    付费下载
    欢迎各位有兴趣的朋友加入Q群:511385161 点评、交流

    展开全文
  • 接下来我对所用的模块以及小车的硬件部分做一个讲解 小车的总体效果图如下: 首先是模块简介: 1、首先就是L298N,这是一个经典的电机驱动,相信基本所有玩过单片机,玩过电机的人都使用过,它可以最高容忍15v...

            接下来我对所用的模块以及小车的硬件部分做一个讲解

            小车的总体效果图如下:


            首先是模块简介:

                1、首先就是L298N,这是一个经典的电机驱动,相信基本所有玩过单片机,玩过电机的人都使用过,它可以最高容忍15v电压输入,逻辑电平2.4-5.5v,所以使用单片机的3.3v完全可以驱动,它并没有PWM接口来控制电机的速度,只能使逻辑电平输出PWM控制通断频率来调节电机的转速,最低驱动电压的话这个没有具体测量,但是6v以上是完全没有问题的。当然,L298N也是有不少的缺点的,比如速度控制的精度差,响应较慢,发热严重,在做电赛的时候使用TB6612驱动,使用效果就更棒了,以后会在大部分的场合使用后者。

               L298N:


            2、红外对管没什么说的,红外可以被反射时低电平,被吸收或距离过远时高电平,上升沿中断就能搞定巡线。

               红外对管

            


            3、HC-SR04,这是一个便宜好用的超声波测距模块,配置简单,但是也有些缺点,首先不能测太小的物体,手掌在50cm左右时测距的效果就不太好了,然后测距的距离不能太远,要不测量时间长不说,精度也不高,最远最好不要超过1m,70cm以内就可以了,然后还有个比较坑的一点,在突然改变与被测物体的距离时,测量值可能突变成无穷。。所以最好加一个软件滤波,均值或者中位数滤波,使距离的测量值更接近真实值。
                HC-SR04:

            4、电源模块,我使用的是一个可以把12v转成5v 3.3v 也可以12v输出,这个模块还是很好用的,但是!!有一个很重要的缺点,实在是太娇贵了,一旦接错或者短路,电压转换芯片直接就烧了,我一个人烧了俩个半。。。这也是给自己鸣了一个警钟,以后在接线以及使用模块的时候一定要小心,毕竟这模块才4元左右,芯片也就几毛钱,但是以后几千的板子烧了后果一定很惨烈。。

                电源模块:


            (这个是已经烧坏的,然后换芯片也没修好)
            5、直流减速电机(TT电机),最最便宜的电机,买车模送的,我在电机接线的地方焊了一个104电容用作去耦,防止电机产生的电流烧坏板子。电机控制的话接入L298N,用PWM控制即可。
                电机:

     104电容:

         

             6、HC-05蓝牙模块,一个很好用的蓝牙透传模块,蓝牙和串口连接,不用去管蓝牙协议,还是很好用的,但是我也遇到了一些些问题,在软件设计中会提到。

                HC-05:


            7、LM2596是一个稳压模块,可以用作降压,允许输入最大电压24v,调节可调电阻就可调节输出电压。
                LM2596:

            8、SG90微型舵机,最小最便宜的塑料舵机,控制的话可以使用PWM也可以用循环+延时反转电平,一开始我用的是PWM,后来因为c8t6的定时器实在有些少,不够给他用。。。换成了循环的方式控制,效果也挺好的,响应也蛮快的。
                SG90:

            9、单片机我使用的是STM32C8T6的核心板,一个特别小的板子,功能强大,价格也便宜,说到价格我就想说说电子大楼的黑心商人,居然把这小板子卖到了30,最后讲价25拿下,网上15以下就可以拿下,能网上买以后还是尽量走淘宝吧以后。。。虽然实验室给报效也是蛮肉疼的。。    

                核心板的引脚图:


            我还为小车焊了一个开关模块,三个拨动开关,三个LED,一个蜂鸣器,三个开关用来做模式的选择,开关打开后LED点亮,蜂鸣器短鸣俩声,然后小车开始工作。

            开关模块如下:


            其次还焊了一个舵机的接线板,以及5v电源的接线板,这俩个很简单只是用了几个引脚而已,图片如下。

            

            

    展开全文
  • 基于STM32的自动跟踪小车

    千次阅读 多人点赞 2019-10-16 19:11:42
    概述 小车外形: 功能简介 利用摄像头识别前车尾部的AprilTag,得到前车位置,传回stm32主控板处理,使两车在行驶时保持恒定距离,实现自动跟车。

    概述

    在这里插入图片描述
    小车外形:
    在这里插入图片描述
    功能简介
    利用摄像头识别前车尾部的AprilTag,得到前车位置,传回stm32主控板处理,使两车在行驶时保持恒定距离,实现自动跟车。

    1.openMV4摄像头

    1.1 Apriltag识别与串口传输

    AprilTag是一个视觉基准库,在AR,机器人,相机校准领域广泛使用。通过特定的标志(与二维码相似,但是降低了复杂度以满足实时性要求),可以快速地检测标志,并计算相对位置。
    Apriltag示例:
    在这里插入图片描述
    通过识别Apriltag,可以得到x,y,z三个方向的距离以及偏移角度。这里只需要三维的距离即可,通过串口传回stm32.

    import sensor, image, time, math,pyb
    from pyb import UART
    
    sensor.reset()
    sensor.set_pixformat(sensor.RGB565)
    sensor.set_framesize(sensor.QQVGA) # we run out of memory if the resolution is much bigger...
    sensor.skip_frames(time = 2000)
    sensor.set_auto_gain(False)  # must turn this off to prevent image washout...
    sensor.set_auto_whitebal(False)  # must turn this off to prevent image washout...
    clock = time.clock()
    uart = UART(3, 115200)#串口波特率
    
    f_x = (2.8 / 3.984) * 160 # find_apriltags defaults to this if not set
    f_y = (2.8 / 2.952) * 120 # find_apriltags defaults to this if not set
    c_x = 160 * 0.5 # find_apriltags defaults to this if not set (the image.w * 0.5)
    c_y = 120 * 0.5 # find_apriltags defaults to this if not set (the image.h * 0.5)
    
    def degrees(radians):
        return (180 * radians) / math.pi
    
    while(True):
        clock.tick()
        img = sensor.snapshot()
        for tag in img.find_apriltags(fx=f_x, fy=f_y, cx=c_x, cy=c_y): # defaults to TAG36H11
            img.draw_rectangle(tag.rect(), color = (255, 0, 0))
            img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0))
            print_args = (tag.x_translation(), tag.y_translation(), tag.z_translation())
                #degrees(tag.x_rotation()), degrees(tag.y_rotation()), degrees(tag.z_rotation()))
            # Translation units are unknown. Rotation units are in degrees.
           # print("Tx %f, Ty %f, Tz %f" % print_args)
            uart.write("A%.2f,B%.2f,C%.2f," % print_args+'\r\n')#设置特定格式,以便于stm32分割取得数据
            #pyb.delay(500)
       # print(clock.fps())
    
    

    2. STM32主控板(型号为F407)

    2.1 时钟与中断配置

    附上stm32时钟示意图:
    在这里插入图片描述
    定时器示意图:
    在这里插入图片描述
    定时器分配:
    在这里插入图片描述
    所有时钟初始化的函数:(每个函数的详细内容在后面)

    TIM8_PWM_Init(400-1,20-1);	//用于控制电机,168M/20=8.4Mhz的计数频率,重装载值400,所以PWM频率为 8.4M/400=21Khz. 
    	TIM3_PWM_Init(200-1,8400-1);//用于控制舵机,50HZ
    	TIM2_Int_Init(400-1,20-1);//定时中断,21KHZ
    	TIM7_Int_Init(500-1,8400-1);//用于编码器计数,20HZ,50ms中断一次
    	uart_init(115200);	//初始化串口1波特率为115200
    	Encoder_Init_TIM4();//编码器接口初始化
    

    2.2 串口收发与数据处理

    串口中断:USART1,USART2
    串口初始化函数(以USART1为例):

    void uart_init(u32 bound){
       //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
     
    	//串口1对应引脚复用映射
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
    	
    	//USART1端口配置
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
    
       //USART1 初始化设置
    	USART_InitStructure.USART_BaudRate = bound;//波特率设置
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
      USART_Init(USART1, &USART_InitStructure); //初始化串口1
    	
      USART_Cmd(USART1, ENABLE);  //使能串口1 
    	
    	USART_ClearFlag(USART1, USART_FLAG_TC);
    	
    #if EN_USART1_RX	
    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
    
    	//Usart1 NVIC 配置
      NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、
    
    #endif
    	
    }
    

    串口中断处理函数:

    void USART1_IRQHandler(void)                	//串口1中断服务程序
    {
    	u8 Res;
    #ifdef OS_TICKS_PER_SEC	 	//如果时钟节拍数定义了,说明要使用ucosII了.
    	OSIntEnter();    
    #endif
    	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    	{
    		Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
    		
    		if((USART_RX_STA&0x8000)==0)//接收未完成
    		{
    			if(USART_RX_STA&0x4000)//接收到了0x0d
    			{
    				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    				else USART_RX_STA|=0x8000;	//接收完成了 
    			}
    			else //还没收到0X0D
    			{	
    				if(Res==0x0d)USART_RX_STA|=0x4000;
    				else
    				{
    					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    					USART_RX_STA++;
    					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
    				}		 
    			}
    		}   		 
      } 
    #ifdef OS_TICKS_PER_SEC	 	//如果时钟节拍数定义了,说明要使用ucosII了.
    	OSIntExit();  											 
    #endif
    } 
    #endif	
    
    

    字符串接收与处理(从openMV接收到的数据):

    /*涉及到的全局变量
    float data[3];//x,y,z方向的距离,浮点数形式
    unsigned char data_string[3][7];//x,y,z方向的距离,字符串形式
    */
    if(USART_RX_STA&0x8000)
    		{	
    			//清空字符串
    			for(i=0;i<2;i++)
    			{
    				for(j=0;j<6;j++)
    				{
    					data_string[i][j]=' ';
    				}
    			}
    			len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
    			for(t=0,j=0;t<len;t++)
    			{
    				j=0;
    				if(USART_RX_BUF[t]=='A')
    				{
    					t++;//去掉首字母
    					while(USART_RX_BUF[t]!=',')
    					{
    						data_string[0][j]=USART_RX_BUF[t];
    						t++;
    						j++;
    					}
    				}
    				if(USART_RX_BUF[t]=='B')
    				{
    					t++;//去掉首字母
    					while(USART_RX_BUF[t]!=',')
    					{
    						data_string[1][j]=USART_RX_BUF[t];
    						t++;
    						j++;
    					}
    				}
    				if(USART_RX_BUF[t]=='C')
    				{
    					t++;//去掉首字母
    					while(USART_RX_BUF[t]!=',')
    					{
    						data_string[2][j]=USART_RX_BUF[t];
    						t++;
    						j++;
    					}
    				}
    			}
    			//把字符串转化为浮点数
    			data[0]=myatof(data_string[0])/100.0;//x
    			data[1]=myatof(data_string[1])/100.0;//y
    			data[2]=myatof(data_string[2])*(-1)/100.0;//z,输入是负数,转换为正
    				//USART2发送数据
    //				Usart_SendString( RS232_USART, (uint8_t *)USART_RX_BUF );
    				
    			//LCD更新显示
    			//显示xyz
    //			  CLEAR(10);
    //			  Gui_DrawFont_GBK16(30,0,BLACK,WHITE,clear);
    //			  Gui_DrawFont_GBK16(30,0,BLACK,WHITE,data_string[0]);
    //				Gui_DrawFont_GBK16(30,20,BLACK,WHITE,clear);
    //			  Gui_DrawFont_GBK16(30,20,BLACK,WHITE,data_string[1]);
    //				Gui_DrawFont_GBK16(30,40,BLACK,WHITE,clear);
    //			  Gui_DrawFont_GBK16(30,40,BLACK,WHITE,data_string[2]);
    				
    				USART_RX_STA=0;//清除标志位
    		}
    }
    

    字符串转化为两位小数浮点数(用于后续PID控制):

    int myatof(const char *str)//此函数仅适用于两位小数的浮点数,返回的是乘100后的int值,因float返回有错误
    {
    	int flag = 1;//表示正数
    	int res =0;
    	u8 i=1; //小数点后两位
    	while(*str != '\0')
    		{
    			if( !(*str >= '0' && *str <= '9'))//找到字符串中的第一个数字
    			{
    				str++;
    				continue;
    			}
    			if(*(str-1) == '-')
    			{
    				flag=-1;//表示是一个负数
    			}
    
    		while(*str >= '0' && *str <= '9')
    		{
    			res = res *10 + (*str - '0');
    			str++;
    		}
    		if(*str == '.')
    		{
    			str++;
    			res = res *10 + (*str - '0');
    			str++;
    			res = res *10 + (*str - '0');//保留两位,故加两次
    			return res*flag;
    		}
    		}
    }
    

    2.3 LCD显示模块

    LCD模块用于调试时观察数据,调试完成可以删去,因为显示屏很耗时,使处理速度变慢
    驱动函数总览:

    void LCD_GPIO_Init(void);
    void Lcd_WriteIndex(u8 Index);
    void Lcd_WriteData(u8 Data);
    void Lcd_WriteReg(u8 Index,u8 Data);
    u16 Lcd_ReadReg(u8 LCD_Reg);
    void Lcd_Reset(void);
    void Lcd_Init(void);
    void Lcd_Clear(u16 Color);
    void Lcd_SetXY(u16 x,u16 y);
    void Gui_DrawPoint(u16 x,u16 y,u16 Data);
    unsigned int Lcd_ReadPoint(u16 x,u16 y);
    void Lcd_SetRegion(u16 x_start,u16 y_start,u16 x_end,u16 y_end);
    void LCD_WriteData_16Bit(u16 Data);
    

    TFT屏幕初始化:

    void TFT_Init_Show(void)
    {
    	Lcd_Clear(WHITE);
    	Gui_DrawFont_GBK16(16,70,BLACK,WHITE,"by WILL CHAN");
    	delay_ms(1000);
    	Lcd_Clear(WHITE);
    	Gui_DrawFont_GBK16(3,0,RED,WHITE,"X:");
    	Gui_DrawFont_GBK16(3,20,RED,WHITE,"Y:");
    	Gui_DrawFont_GBK16(3,40,RED,WHITE,"Z:");
    	Gui_DrawFont_GBK16(3,60,RED,WHITE,"speed:");
    }
    

    字符串显示函数;

    void Gui_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s)
    {
    	unsigned char i,j;
    	unsigned short k,x0;
    	x0=x;
    
    	while(*s) 
    	{	
    		if((*s) < 128) 
    		{
    			k=*s;
    			if (k==13) 
    			{
    				x=x0;
    				y+=16;
    			}
    			else 
    			{
    				if (k>32) k-=32; else k=0;
    	
    			    for(i=0;i<16;i++)
    				for(j=0;j<8;j++) 
    					{
    				    	if(asc16[k*16+i]&(0x80>>j))	Gui_DrawPoint(x+j,y+i,fc);
    						else 
    						{
    							if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);
    						}
    					}
    				x+=8;
    			}
    			s++;
    		}
    			
    		else 
    		{
    		
    
    			for (k=0;k<hz16_num;k++) 
    			{
    			  if ((hz16[k].Index[0]==*(s))&&(hz16[k].Index[1]==*(s+1)))
    			  { 
    				    for(i=0;i<16;i++)
    				    {
    						for(j=0;j<8;j++) 
    							{
    						    	if(hz16[k].Msk[i*2]&(0x80>>j))	Gui_DrawPoint(x+j,y+i,fc);
    								else {
    									if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);
    								}
    							}
    						for(j=0;j<8;j++) 
    							{
    						    	if(hz16[k].Msk[i*2+1]&(0x80>>j))	Gui_DrawPoint(x+j+8,y+i,fc);
    								else 
    								{
    									if (fc!=bc) Gui_DrawPoint(x+j+8,y+i,bc);
    								}
    							}
    				    }
    				}
    			  }
    			s+=2;x+=16;
    		} 
    		
    	}
    }
    

    2.4 电机、舵机与编码器

    定时中断:TIM2,用于修改电机和舵机的PWM占空比
    初始化函数:

    //通用定时器2中断初始化
    //arr:自动重装值。
    //psc:时钟预分频数
    //定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
    //Ft=定时器工作频率,单位:Mhz
    //这里使用的是定时器2!
    void TIM2_Int_Init(u16 arr,u16 psc)
    {
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  ///使能TIM2时钟
    	
      TIM_TimeBaseInitStructure.TIM_Period = arr; 	//自动重装载值
    	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
    	
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM3
    	
    	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器2更新中断
    	TIM_Cmd(TIM2,ENABLE); //使能定时器2
    	
    	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器2中断
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority=2; //子优先级3
    	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    	NVIC_Init(&NVIC_InitStructure);
    
    }
    

    TIM2中断处理函数:

    void TIM2_IRQHandler(void)
    {	
    	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//溢出中断
    	{
    		if(motor_flag==1)//反转
    		{
    			TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC6
    			TIM_SetCompare2(TIM8,0);
    		}
    		if(motor_flag==0)//正转
    		{
    			TIM_SetCompare1(TIM8,0);
    			TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC7
    		}
    		TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//设置舵机角度,引脚:PA6
    	}
    	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清除中断标志位
    }
    

    PWM输出:TIM3(舵机),TIM8(电机)
    初始化函数(以TIM8为例):

    void TIM8_PWM_Init(u32 arr,u32 psc)
    {		 					 
    	//此部分需手动修改IO口设置
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    	TIM_OCInitTypeDef  TIM_OCInitStructure;
    	TIM_BDTRInitTypeDef	TIM_BDTRInitStructure;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);  	//TIM8时钟使能    
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE); 	//使能PORTA时钟	
    	
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;           //GPIOFA
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度100MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure);              //初始化PA7
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
      GPIO_Init(GPIOB,&GPIO_InitStructure);
    	
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
      GPIO_Init(GPIOC,&GPIO_InitStructure);
    	
    	GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM8); //GPIOA8复用为定时器1
    	GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOA9复用为定时器1
    	GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8); //GPIOA10复用为定时器1
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOB13复用为定时器1
    	GPIO_PinAFConfig(GPIOB,GPIO_PinSource0,GPIO_AF_TIM8); //GPIOB14复用为定时器1
    	GPIO_PinAFConfig(GPIOB,GPIO_PinSource1,GPIO_AF_TIM8); //GPIOB15复用为定时器1
    
    	TIM_TimeBaseStructure.TIM_Prescaler=psc;  //定时器分频
    	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    	TIM_TimeBaseStructure.TIM_Period=arr;   //自动重装载值
    	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
    	
    	TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);//初始化定时器1
    	
    	//初始化PWM模式	 
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    	TIM_OCInitStructure.TIM_Pulse = 0;
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; 
    	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
    
    	TIM_OC1Init(TIM8, &TIM_OCInitStructure);
    	TIM_OC2Init(TIM8, &TIM_OCInitStructure);
    	TIM_OC3Init(TIM8, &TIM_OCInitStructure);
    
    	TIM_OC1PreloadConfig(TIM8,TIM_OCPreload_Enable);
    	TIM_OC2PreloadConfig(TIM8,TIM_OCPreload_Enable);
    	TIM_OC3PreloadConfig(TIM8,TIM_OCPreload_Enable);
    
    	TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    	TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    	TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
    	TIM_BDTRInitStructure.TIM_DeadTime = 0;
    	TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; 
    	TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low;
    	TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable;
    	TIM_BDTRConfig(TIM8,&TIM_BDTRInitStructure);
    
    	TIM_Cmd(TIM8,ENABLE);
    	TIM_CCPreloadControl(TIM8,ENABLE);
    	TIM_CtrlPWMOutputs(TIM8,ENABLE);
    										  
    }  
    
    

    编码器初始化函数:

    void Encoder_Init_TIM4(void)
    {
        GPIO_InitTypeDef         GPIO_InitStructure; 
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        TIM_ICInitTypeDef        TIM_ICInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
     
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//开启TIM4时钟
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//开启GPIOB时钟
      
        GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);//PB6引脚复用
        GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);//PB7引脚服用
     
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //GPIOB6,GPIOB7
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
        //GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL ;
    	  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        GPIO_Init(GPIOB,&GPIO_InitStructure); 
        
        NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /*它的抢占优先级可以尽量设置低点*/
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;//禁用中断,防止计数溢出而没有相应函数,造成卡死
        NVIC_Init(&NVIC_InitStructure);
     
     
     
        TIM_TimeBaseStructure.TIM_Period = 4095; //设置下一个更新事件装入活动的自动重装载寄存器周期的值
        TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值  不分频
        TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
        TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 
     
        TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Falling);
        TIM_ICStructInit(&TIM_ICInitStructure);
        TIM_ICInitStructure.TIM_ICFilter = 10;  //输入滤波器
        TIM_ICInit(TIM4, &TIM_ICInitStructure);
        TIM_ClearFlag(TIM4, TIM_FLAG_Update);  //清除所有标志位
        TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE); //允许中断更新
        TIM4->CNT = 0;
        TIM_Cmd(TIM4, ENABLE);  //使能TIM4
    }
    
    

    编码器返回速度值:

    /**************************************************************************
    函数功能:单位时间读取编码器计数
    入口参数:定时器
    返回  值:速度值
    **************************************************************************/
    float Read_Encoder_Speed(uint8_t TIMX)
    {
        int32_t Encoder_TIM;
        float res = 0;
        switch (TIMX)
        {
        case 5:
            Encoder_TIM = TIM_GetCounter(TIM5);
            TIM5->CNT = ENCODER_BASE_COUNT; 
            res = (int32_t)Encoder_TIM - ENCODER_BASE_COUNT;
            break;
        case 4:
            Encoder_TIM = TIM_GetCounter(TIM4);
            TIM4->CNT = ENCODER_BASE_COUNT; 
            res = (int32_t)Encoder_TIM - ENCODER_BASE_COUNT;
            break;
        default:
            Encoder_TIM = 0;
            res = 0;
        }
        if(res>2048.0f)
            res-=4096.0f;
        return res*360.0f/4096.0f;
    }
    

    定时从编码器取数,注意,时间不一样,取回的数值也不一样,取决于实际速度以及编码器线数。这里50ms取一次:

    void TIM7_IRQHandler(void)//频率20HZ,用于编码器计数
    {	
    	if(TIM_GetITStatus(TIM7,TIM_IT_Update)==SET)//溢出中断
    	{
    		speed=Read_Encoder_Speed(4);
    	}
    	TIM_ClearITPendingBit(TIM7,TIM_IT_Update);  //清除中断标志位
    }
    

    2.5 PID控制

    PID库函数:

    #define N 2				//需要对多少变量进行pid调节
    
    const float KP[N]={1.3,1.0};//这里只用了比例调节
    const float KI[N]={0,0};
    const float KD[N]={0,0};
    
    
    struct _pid{
    	float SetVol;				//定义设定值
    	float ActVol;				//定义实际值
    	float Err;					//定义误差
    	float	Err_Next;			//定义上一个误差
    	float	Err_Last;			//定义上上一个误差
    	float Kp,Ki,Kd;			//定义比例、积分、微分系数
    	float integral;			//定义积分值
    	float actuator;			//定义控制器执行变量
    }pid[N];
    
    void PID_Init(void)
    {
    	for(int i=0;i<N;i++)
    	{
    		pid[i].SetVol=0.0;
    		pid[i].ActVol=0.0;
    		pid[i].Err=0.0;
    		pid[i].Err_Next=0.0;
    		pid[i].Err_Last=0.0;
    		pid[i].integral=0.0;
    		pid[i].actuator=0.0;
    		pid[i].Kp=KP[i];
    		pid[i].Ki=KI[i];
    		pid[i].Kd=KD[i];
    	}
    }
    
    float PID_realize(float set_val,float get_val,int i)			//位置型PID算法实现
    {
    	pid[i].SetVol=set_val;
    	pid[i].ActVol=get_val;
    	pid[i].Err=pid[i].SetVol-pid[i].ActVol;
    	float IncVol;		//定义增量
    	pid[i].integral+=pid[i].Err;
    //	IncVol=pid[i].Kp*(pid[i].Err-pid[i].Err_Next)+pid[i].Ki*pid[i].Err+pid[i].Kd*(pid[i].Err-2*pid[i].Err_Next+pid[i].Err_Last);
    	pid[i].actuator=pid[i].Kp* pid[i].Err+pid[i].Ki*pid[i].integral+pid[i].Kd*(pid[i].Err-pid[i].Err_Next);
    //	pid[i].actuator=adc_val+IncVol;
    	pid[i].ActVol=pid[i].actuator;
    	pid[i].Err_Last=pid[i].Err_Next;
    	pid[i].Err_Next=pid[i].Err;
    	
    	return pid[i].actuator;
    }
    

    主函数中的PID调节:

    		z_get=data[2];
    		x_get=data[0];
    		if(z_get-z_set>0.5||z_get-z_set<-0.5)//电机PID
    		{
    			LED1=0;	//调节时灯亮
    			PID_val_motor=PID_realize(z_set,z_get,0);
    			PID_val_motor=PID_val_motor/10.0;
    			if(PID_val_motor<=0)
    				motor_flag=0;//motor_flag控制电机正反转,PID_val_motor用于改变占空比,范围0~1
    			if(PID_val_motor>0)
    				motor_flag=1;
    			PID_val_motor=abs_float(PID_val_motor);
    			if(PID_val_motor>2)PID_val_motor=0;//标志太远,让车停止
    			if(PID_val_motor>1&&PID_val_motor<=2)PID_val_motor=1;
    			if(PID_val_motor<0.2)PID_val_motor=0;
    		}
    		if(x_get-x_set>0.1||x_get-x_set<-0.1)//舵机PID
    		{
    			LED1=0;	
    			PID_val_servo=PID_realize(x_set,x_get,1);
    			servo_angle=((140-35)/6)*PID_val_servo+35;//线性映射,把PID的值转化为角度35~140的舵机转角
    			if(servo_angle<35)servo_angle=35;
    			if(servo_angle>140)servo_angle=140;
    		}
    		LED1=1;
    

    定时器TIM2中断里改变占空比:

    void TIM2_IRQHandler(void)
    {	
    	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//溢出中断
    	{
    		if(motor_flag==1)//反转
    		{
    			TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC6
    			TIM_SetCompare2(TIM8,0);
    		}
    		if(motor_flag==0)//正转
    		{
    			TIM_SetCompare1(TIM8,0);
    			TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//和定时器的自动重装载值进行比较,来设置占空比,引脚:PC7
    		}
    		TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//设置舵机角度,根据舵机手册得到占空比与转角的关系,引脚:PA6
    	}
    	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清除中断标志位
    }
    

    3.电源与电机驱动

    3.1 L298N电机驱动板

    在这里插入图片描述
    因为后面两路电机要求同速,故把AB两通道用线短接,用一路PWM控制两路电机
    下面是使用说明:
    在这里插入图片描述
    具体控制代码见上面TIM2中断处理函数中,利用两路定时器轮流输出PWM(另一路为零),即可控制电机正反转

    3.2 LM2596降压模块

    在这里插入图片描述
    手册中的典型连接:
    在这里插入图片描述
    原理图如下:
    在这里插入图片描述

    3.3 电源部分注意事项

    1.电池用的是12v航模锂电池,为了防止过放导致电池损坏,必须要在电池输入端加一个电压表模块,如下图:
    在这里插入图片描述
    2.控制部分电源和电机舵机电源分开,因为电机舵机启动时会过大电流,导致电压不稳定,影响芯片供电。这里LM2596给电机供电,一个LM2596给舵机供电,另一个LM2596给单片机和openMV供电

    3.控制电源和电机舵机电源分别加开关,下程序的时候先关闭电机和舵机的电源。因为此时控制器没有给信号,电机和舵机可能会不受控制的运动

    展开全文
  • 通过STM32核心板控制电机驱动模块,JDY-31蓝牙模块与手机通讯。 总共三块电池,一块专门给单片机供电,另外两块串联在一起同时给电机驱动模块供电(为什么选择分别供电后面的电源硬件部分有说)。 硬件 硬件...
  • 毕设分享:STM32两轮自平衡小车系统设计与控制 含源码、原理图及PCB文件
  • stm32 智能避障小车(一)

    千次阅读 热门讨论 2019-02-14 16:33:14
    避障小车制作过程 今年寒假期间,我再csdn上看到别人制作的避障小车,我想了想,越想越觉得好玩,于是在这20多天里,我基本已经做到我想要的效果了,这期间我也是很烦恼,很简单的制作,我花费了这么长的时间,我...
  • STM32智能小车之 循迹

    万次阅读 多人点赞 2014-11-15 23:49:44
    stm32实现循迹小车
  • 平衡小车,意思就是两个轮子的小车,能够直挺挺的直立不倒。 首先准备材料 首先底盘和电机。 底盘和电机一套用的是平衡小车之家的。 因为电机是编码器直流电机的原因所有一套稍微贵点,不过对于真正想玩的人来说,这...
  • 项目名称:基于STM32f103简单自平衡小车 项目完成时间:2018.9.20——2018.10.20 项目类别:自动控制类 项目总体概述:  这个项目是我在入门嵌入式做的第一个项目,是在看完原子哥的视频后,为了巩固和连接起来...
  • STM32循迹+蓝牙小车

    千次阅读 多人点赞 2016-11-27 16:51:30
    stm32智能循迹小车+蓝牙控制+代码
  • STM32智能小车循迹模块程序

    万次阅读 2018-05-18 15:24:34
    //LED + BEEP + 按键 实验测试#include "key.h"#include "led.h"#include "delay.h"#include "tracking.h"#include "motor.h"#include "...
  • 实体作品请参看优酷视频。 ...说明: 该作品为arvik于2014年下半年在学校实验室做的一个国家级大学生科技创新项目,大概花了两个多月课余时间完成。 其实博客早在几个月前就写好了,一共3篇,后来发现第一篇丢失。...
  • 基于STM32F103循迹小车

    万次阅读 多人点赞 2018-08-27 16:47:42
    我这次想要跟大家分享的是基于STM32F103的循迹小车的制作。(两个轮子) 主要硬件:板子、L298N、电机、4个循迹模块等 话不多说,先上干货。 XJ.c #include "XJ.h"//循迹文件 #include "stm32f10...
  • 基于STM32wifi小车制作(三)

    千次阅读 2017-05-24 23:51:06
    这次还是硬件电路的焊接,增加了OLED接口,舵机接口,超声波模块接口,还有LED灯珠和继电器连接接口。 需要注意的是:  1、舵机采用的是9G舵机,为了防止出现舵机抖动,在信号脚焊了一个4.7K的上拉电阻。...
  • stm32智能小车之路之小车启动

    千次阅读 多人点赞 2014-10-20 23:33:13
    stm32智能小车之---小车启动
  • stm32智能小车开发/超声波

    千次阅读 2018-05-20 13:23:59
    学习了stm32单片机后,总觉得只有一块板子玩着有点无聊,尝试着搭过一些外设,但还是觉得有些不得劲。于是,萌发了自己做个小车的想法,也算是将32单片机的知识再做一次回顾、复习我选择的是四驱小车,感觉四驱更爽...
  • 基于STM32F103C8T6的循迹避障小车完整制作过程 由于本人的一个小项目,要做一个基于STM32的循迹避障小车,前后花了约1周的时间,这个过程种也参考了很多大神分享的资料,学到很多的东西。但是资料都比较分散,有些...
1 2 3 4 5 ... 20
收藏数 2,201
精华内容 880
关键字:

stm32 小车