Development of an Automatic Tracking Car Based on STM32

Development of an Automatic Tracking Car Based on STM32

There is a small interactive gift at the end of the article, don’t miss it❤️

Development of an Automatic Tracking Car Based on STM32
Overview
Car Appearance:
Development of an Automatic Tracking Car Based on STM32
Function Introduction Utilizes a camera to recognize the rear of the car ahead with an AprilTag, obtains the position of the front car, sends it back to the STM32 main control board for processing, allowing the two cars to maintain a constant distance while driving, achieving automatic following.
OpenMV4 Camera
1.1 AprilTag Recognition and Serial Transmission
AprilTag is a visual marker library widely used in AR, robotics, and camera calibration. By using specific markers (similar to QR codes but simplified to meet real-time requirements), it can quickly detect markers and calculate relative positions.
Example of AprilTag:
Development of an Automatic Tracking Car Based on STM32
By recognizing the AprilTag, distances in the x, y, and z directions and the offset angle can be obtained. Here, only the three-dimensional distance is needed, which is sent back to the STM32 via serial port.
import sensor, image, time, math,pybfrom 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)#Serial port baud rate
f_x = (2.8 / 3.984) * 160 # find_apriltags defaults to this if not setf_y = (2.8 / 2.952) * 120 # find_apriltags defaults to this if not setc_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')#Set specific format for STM32 to segment and obtain data#pyb.delay(500)# print(clock.fps())
STM32 Main Control Board (Model F407)
2.1 Clock and Interrupt Configuration
Attached is the STM32 clock diagram:
Development of an Automatic Tracking Car Based on STM32
Timer diagram:
Development of an Automatic Tracking Car Based on STM32
Timer allocation:
Development of an Automatic Tracking Car Based on STM32
All clock initialization functions: (Detailed content of each function is below)
TIM8_PWM_Init(400-1,20-1);  //Used to control motors, 168M/20=8.4Mhz counting frequency, reload value is 400, so PWM frequency is 8.4M/400=21Khz.   TIM3_PWM_Init(200-1,8400-1);//Used to control servos, 50HZ  TIM2_Int_Init(400-1,20-1);//Timer interrupt, 21KHZ  TIM7_Int_Init(500-1,8400-1);//Used for encoder counting, 20HZ, interrupt once every 50ms  uart_init(115200);  //Initialize serial port 1 baud rate to 115200  Encoder_Init_TIM4();//Encoder interface initialization
2.2 Serial Transmission and Data Processing
Serial Interrupt: USART1, USART2 serial initialization function (taking USART1 as an example):
void uart_init(u32 bound){//GPIO port settings  GPIO_InitTypeDef GPIO_InitStructure;  USART_InitTypeDef USART_InitStructure;  NVIC_InitTypeDef NVIC_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //Enable GPIOA clock  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//Enable USART1 clock
//Serial port 1 corresponding pin remapping  GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9 remapping to USART1  GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10 remapping to USART1
//USART1 port configuration  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9 and GPIOA10  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//Multiplexing function  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  //Speed 50MHz  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //Push-pull multiplexing output  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //Pull-up  GPIO_Init(GPIOA,&GPIO_InitStructure); //Initialize PA9, PA10
//USART1 Initialization Settings  USART_InitStructure.USART_BaudRate = bound;//Baud rate settings  USART_InitStructure.USART_WordLength = USART_WordLength_8b;//Word length is 8 bits data format  USART_InitStructure.USART_StopBits = USART_StopBits_1;//One stop bit  USART_InitStructure.USART_Parity = USART_Parity_No;//No parity bit  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//No hardware data flow control  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  //Receive and transmit mode  USART_Init(USART1, &USART_InitStructure); //Initialize serial port 1
  USART_Cmd(USART1, ENABLE);  //Enable serial port 1 
  USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//Enable related interrupts
//Usart1 NVIC configuration  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//Serial port 1 interrupt channel  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//Preemptive priority 3  NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;    //Sub-priority 3  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      //IRQ channel enable  NVIC_Init(&NVIC_InitStructure);  //Initialize VIC register according to specified parameters,
#endif
}
Serial interrupt handling function:
void USART1_IRQHandler(void)                  //Serial port 1 interrupt service program{  u8 Res;#ifdef OS_TICKS_PER_SEC     //If the clock tick count is defined, it indicates that ucosII is to be used.  OSIntEnter();    #endifif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //Receive interrupt (the received data must end with 0x0d 0x0a)  {    Res =USART_ReceiveData(USART1);//(USART1->DR);  //Read the received data
if((USART_RX_STA&0x8000)==0)//Receiving not completed    {if(USART_RX_STA&0x4000)//Received 0x0d      {if(Res!=0x0a)USART_RX_STA=0;//Reception error, restart else USART_RX_STA|=0x8000;  //Reception completed       }else //Not yet received 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;//Reception data error, restart reception            }           }    }          } #ifdef OS_TICKS_PER_SEC     //If the clock tick count is defined, it indicates that ucosII is to be used.  OSIntExit();                         #endif}
#endif
String reception and processing (data received from openMV):
/*Global variables involved float data[3];//Distances in x, y, z directions, in floating point formatunsigned char data_string[3][7];//Distances in x, y, z directions, in string format*/if(USART_RX_STA&0x8000)    {  //Empty stringfor(i=0;i<2;i++)      {for(j=0;j<6;j++)        {          data_string[i][j]=' ';        }      }      len=USART_RX_STA&0x3fff;//Get the length of the data received this timefor(t=0,j=0;t<len;t++)      {        j=0;if(USART_RX_BUF[t]=='A')        {          t++;//Remove the first letterwhile(USART_RX_BUF[t]!=',')          {            data_string[0][j]=USART_RX_BUF[t];            t++;            j++;          }        }if(USART_RX_BUF[t]=='B')        {          t++;//Remove the first letterwhile(USART_RX_BUF[t]!=',')          {            data_string[1][j]=USART_RX_BUF[t];            t++;            j++;          }        }if(USART_RX_BUF[t]=='C')        {          t++;//Remove the first letterwhile(USART_RX_BUF[t]!=',')          {            data_string[2][j]=USART_RX_BUF[t];            t++;            j++;          }        }      }//Convert string to floating point data[0]=myatof(data_string[0])/100.0;//xdata[1]=myatof(data_string[1])/100.0;//ydata[2]=myatof(data_string[2])*(-1)/100.0;//z, input is negative, convert to positive//USART2 send data//        Usart_SendString( RS232_USART, (uint8_t *)USART_RX_BUF );
//LCD update display//Show 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;//Clear the flag bit    }}
String conversion to two decimal floating point (for subsequent PID control):
int myatof(const char *str)//This function is only suitable for floating point numbers with two decimal places, returns the int value multiplied by 100, as float return has errors{int flag = 1;//Indicates positive numberint res =0;  u8 i=1; //Two decimal places after the decimal pointwhile(*str != '\0')    {if( !(*str >= '0' && *str <= '9'))//Find the first number in the string      {        str++;continue;      }if(*(str-1) == '-')      {        flag=-1;//Indicates a negative number      }
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');//Keep two digits, so add twicereturn res*flag;    }}
2.3 LCD Display Module
The LCD module is used to observe data during debugging; it can be removed after debugging as the display is time-consuming and slows down processing. Overview of driving functions:
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);
Initialization of TFT screen:
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:");}
String display function:
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 Motors, Servos, and Encoders
Timer interrupt: TIM2, used to modify the PWM duty cycle of motors and servos initialization function:
//Generic timer 2 interrupt initialization//arr: auto reload value.//psc: clock prescaler//Timer overflow time calculation method: Tout=((arr+1)*(psc+1))/Ft us.//Ft=Timer working frequency, unit:Mhz//Here Timer 2 is used!void TIM2_Int_Init(u16 arr,u16 psc){  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  NVIC_InitTypeDef NVIC_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  ///Enable TIM2 clock
  TIM_TimeBaseInitStructure.TIM_Period = arr;   //Auto reload value  TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //Timer division  TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //Up count mode  TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
  TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//Initialize TIM3
  TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //Allow timer 2 update interrupt  TIM_Cmd(TIM2,ENABLE); //Enable timer 2
  NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //Timer 2 interrupt  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //Preemptive priority 1  NVIC_InitStructure.NVIC_IRQChannelSubPriority=2; //Sub-priority 3  NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  NVIC_Init(&NVIC_InitStructure);
}
TIM2 interrupt handling function:
void TIM2_IRQHandler(void){  if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//Overflow interrupt  {if(motor_flag==1)//Reverse    {      TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//Compare with the timer's auto reload value to set the duty cycle, pin:PC6      TIM_SetCompare2(TIM8,0);    }if(motor_flag==0)//Forward    {      TIM_SetCompare1(TIM8,0);      TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//Compare with the timer's auto reload value to set the duty cycle, pin:PC7    }    TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//Set servo angle, pin:PA6  }  TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //Clear interrupt flag}
PWM Output: TIM3 (Servo), TIM8 (Motor) initialization function (taking TIM8 as an example):
void TIM8_PWM_Init(u32 arr,u32 psc){                //This part needs manual modification of IO port settings
  GPIO_InitTypeDef GPIO_InitStructure;  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;  TIM_OCInitTypeDef  TIM_OCInitStructure;  TIM_BDTRInitTypeDef  TIM_BDTRInitStructure;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);    //Enable TIM8 clock      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE);   //Enable PORTA clock  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;           //GPIOFA  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //Multiplexing function  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  //Speed 100MHz  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //Push-pull multiplexing output  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //Pull-up  GPIO_Init(GPIOA,&GPIO_InitStructure);              //Initialize 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 remapping to timer 1  GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOA9 remapping to timer 1  GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8); //GPIOA10 remapping to timer 1  GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOB13 remapping to timer 1  GPIO_PinAFConfig(GPIOB,GPIO_PinSource0,GPIO_AF_TIM8); //GPIOB14 remapping to timer 1  GPIO_PinAFConfig(GPIOB,GPIO_PinSource1,GPIO_AF_TIM8); //GPIOB15 remapping to timer 1
  TIM_TimeBaseStructure.TIM_Prescaler=psc;  //Timer division  TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //Up count mode  TIM_TimeBaseStructure.TIM_Period=arr;   //Auto reload value  TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
  TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);//Initialize timer 1
//Initialize PWM mode     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);
}
Encoder initialization function:
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);//Enable TIM4 clock    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//Enable GPIOB clock
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);//PB6 pin remapping    GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);//PB7 pin remapping
    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; /*Its preemptive priority can be set lower*/    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;//Disable interrupt to prevent counting overflow without corresponding function, causing deadlock    NVIC_Init(&NVIC_InitStructure);

    TIM_TimeBaseStructure.TIM_Period = 4095; //Set the value loaded into the active auto-reload register for the next update event    TIM_TimeBaseStructure.TIM_Prescaler = 0; //Set the prescaler value for the TIMx clock frequency, no division    TIM_TimeBaseStructure.TIM_ClockDivision = 0; //Set clock division: TDTS = Tck_tim    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM counts up mode    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;  //Input filter    TIM_ICInit(TIM4, &TIM_ICInitStructure);    TIM_ClearFlag(TIM4, TIM_FLAG_Update);  //Clear all flags    TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE); //Allow update interrupt    TIM4->CNT = 0;    TIM_Cmd(TIM4, ENABLE);  //Enable TIM4}
Encoder returns speed value:
/**************************************************************************Function function: Read encoder count within unit time Entry parameters: Timer returns Value: speed value**************************************************************************/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;}
Regularly fetch data from the encoder, note that the time is different, the returned value is also different, depending on the actual speed and the number of encoder lines. Here, once every 50ms:
void TIM7_IRQHandler(void)//Frequency 20HZ, used for encoder counting{  if(TIM_GetITStatus(TIM7,TIM_IT_Update)==SET)//Overflow interrupt  {    speed=Read_Encoder_Speed(4);  }  TIM_ClearITPendingBit(TIM7,TIM_IT_Update);  //Clear interrupt flag}
2.5 PID Control
PID library function:
#define N 2        //How many variables need PID adjustment
const float KP[N]={1.3,1.0};//Only proportional adjustment is used hereconst float KI[N]={0,0};const float KD[N]={0,0};
struct _pid{float SetVol;        //Define set valuefloat ActVol;        //Define actual valuefloat Err;          //Define errorfloat  Err_Next;      //Define previous errorfloat  Err_Last;      //Define the error before the last onefloat Kp,Ki,Kd;      //Define proportional, integral, derivative coefficientsfloat integral;      //Define integral valuefloat actuator;      //Define controller execution variable}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)      //Position type PID algorithm implementation{  pid[i].SetVol=set_val;  pid[i].ActVol=get_val;  pid[i].Err=pid[i].SetVol-pid[i].ActVol;float IncVol;    //Define increment  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 adjustment in the main function:
    z_get=data[2];    x_get=data[0];if(z_get-z_set>0.5||z_get-z_set<-0.5)//Motor PID    {      LED1=0;  //The light is on during adjustment      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 controls the motor forward and reverse, PID_val_motor is used to change the duty cycle, range 0~1if(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 the mark is too far, stop the carif(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)//Servo PID    {      LED1=0;        PID_val_servo=PID_realize(x_set,x_get,1);      servo_angle=((140-35)/6)*PID_val_servo+35;//Linear mapping, converting PID value to angle 35~140 of servo rotationif(servo_angle<35)servo_angle=35;if(servo_angle>140)servo_angle=140;    }    LED1=1;
Change duty cycle in the TIM2 interrupt:
void TIM2_IRQHandler(void){  if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//Overflow interrupt  {if(motor_flag==1)//Reverse    {      TIM_SetCompare1(TIM8,motor_duty*PID_val_motor*400.0);//Compare with the timer's auto reload value to set the duty cycle, pin:PC6      TIM_SetCompare2(TIM8,0);    }if(motor_flag==0)//Forward    {      TIM_SetCompare1(TIM8,0);      TIM_SetCompare2(TIM8,motor_duty*PID_val_motor*400.0);//Compare with the timer's auto reload value to set the duty cycle, pin:PC7    }    TIM_SetCompare1(TIM3,200-(servo_angle/45.0+1)*5);//Set servo angle, according to the servo manual to obtain the relationship between duty cycle and angle, pin:PA6  }  TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //Clear interrupt flag}
Power Supply and Motor Drive
3.1 L298N Motor Driver Board
Development of an Automatic Tracking Car Based on STM32
Since the last two motors require the same speed, connect channels A and B with wires, and control both motors with one PWM.
Below is the usage instructions:
Development of an Automatic Tracking Car Based on STM32
The specific control code can be seen in the above TIM2 interrupt handling function, using two timers to output PWM alternately (the other one is zero), which can control the motor to rotate forward and backward.
3.2 LM2596 Buck Module
Development of an Automatic Tracking Car Based on STM32
Typical connections in the manual:
Development of an Automatic Tracking Car Based on STM32
The schematic diagram is as follows:
Development of an Automatic Tracking Car Based on STM32
3.3 Power Supply Precautions
1.The battery used is a 12v lithium battery for models. To prevent over-discharge and damage to the battery, a voltmeter module must be added at the battery input, as shown below:
Development of an Automatic Tracking Car Based on STM32
2.Separate the control power supply and the motor servo power supply, as the motor servo will draw a large current at startup, causing voltage instability, affecting chip power supply. Here, one LM2596 supplies power to the motor, another LM2596 supplies power to the servo, and another LM2596 supplies power to the microcontroller and openMV.
3.Control power and motor servo power should each have a switch. When programming, first turn off the power to the motor and servo. Because at this time the controller does not give a signal, the motor and servo may move uncontrollably.
Disclaimer: The materials in this article are sourced from the internet, and the copyright belongs to the original author. If there are copyright issues, please contact me for deletion.
🙋♂️Today’s Interaction Gift
Share the poster below to your circle of friends, screenshot it and send it to Xiao Chuang’s WeChat (wade15611338907) for a chance to win a copy of the “Embedded Linux Driver Development Tutorial” worth 69 yuan, Deadline: Tomorrow at 18:00.

A small gift, hope you like it❤️

Development of an Automatic Tracking Car Based on STM32
Development of an Automatic Tracking Car Based on STM32

1

《【真香】嵌入式物联网/硬件开发工程师们的工作台原来是这样的……》

2

《搞嵌入式Linux,做底层还是应用?做底层要掌握哪些技能?》

3

《有一点嵌入式基础不想做单片机,如何打怪升级?》

Development of an Automatic Tracking Car Based on STM32

Development of an Automatic Tracking Car Based on STM32

Development of an Automatic Tracking Car Based on STM32
Development of an Automatic Tracking Car Based on STM32
Click to share
Development of an Automatic Tracking Car Based on STM32
Click to like
Development of an Automatic Tracking Car Based on STM32
Click to view

Leave a Comment