这是第二个完整的控制组小车题目,是21年的电赛F题

以下是赛题的任务部分,要求双车协同,并且能识别数字完成对应任务。

基于STM32的智能送药小车

本设计采用STM32单片机作为主控模块,打造了一款智能送药小车控制系统。该系统主要整合了OpenMV机器视觉模块、蓝牙通信模块、电机驱动和电源模块。通过OpenMV机器视觉,系统能够精准采集图像并进行模板匹配,生成专属的数字检测模型。两轮小车的设计提高了转向灵活性,同时有效减少占用空间,适合在狭窄环境中运行。蓝牙模块确保了稳定的远程通信,而高效电机驱动则保障了小车的平稳快速行驶。该系统不仅技术先进,而且实用性强,为医疗输送提供了智能化解决方案。

系统方案

本小车系统主要由循迹模块、数字识别模块、药品检测模块、电机控制模块组组成,下面先分别论证模块的选择

循迹模块

方案一:使用Openmv机器视觉模块。将摄像头拍摄到的图片转化为灰度图并划分为6部分,在不同区域分别确定色块位置,根据位置判定十字路口,丁字路口和直线。在直线运动阶段,通过直线色块中心点坐标与图像中心点坐标之差确定误差值,传输给下位机调节PID。

方案二:使用红外传感器。通过接收器接收红色电工胶带发出的红外线,根据接收器信号确定小车与路径相对位置,通过PID调节位置偏移量。红外传感器对于红色路径识别度较低,故舍弃改方案。

方案三:使用灰度传感器。使用高亮LED灯,通过接收管对不同强度反射光的强弱处理对比区分出路径,将接收器接收到的信号传入STM32中,通过PID调节位置偏移量。灰度传感器容易受到环境影响,所以舍弃该方案。

为保证识别精度,确保巡线的稳定性,所以选择方案一。

数字识别模块

方案一:使用k210机器视觉模块。通过配置环境以及制作数据集进行本地训练,随着数据集中数据量的增大,识别精度也随之升高。且k210环境配置简单,训练方便。

方案二:使用树莓派模块。通过配置环境,编写树莓派代码以及制作数据集进行训练,相较于k210数字识别,树莓派环境配置更繁琐,且安装繁琐。

方案三:使用Openmv摄像头进行模板匹配,匹配到模板的数字后返回给单片机,由单片机执行对应操作

组内仅有Openmv摄像头,所以选择方案三。

电机控制模块

因为这次使用的电机由直流电机换成了步进电机,所以驱动模块由TB6612改成了步进电机双路驱动底板(D302A),本驱动底板使用的芯片为 LV8731V 步进电机驱动芯片。

小车实物

程序设计

mian.c

包含模块化程序设计

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "drv_Main.hpp"
#include "drv_LED.hpp"
#include "drv_LCD.hpp"
#include "drv_Key.hpp"
#include "Commulink.hpp"
#include "drv_MPU6050.hpp"
#include "Control.hpp"
#include "GUI.hpp"
#include "Show.hpp"
#include "Sg90.hpp"
/*
 * WHU-CAR 接口说明
 * PD4  ---> AIN1
 * PC12 ---> AIN2
 * PD11 ---> BIN1
 * PD10 ---> BIN2
 * PE10 ---> STBY
 * PD12 ---> E2A  T4C1
 * PD13 ---> E2B  T4C2
 * PC6  ---> E1A  T3C1
 * PC7  ---> E1B  T3C2
 * PE14 ---> PWMA T1C4
 * PE13 ---> PWMB T1C3
 * PC2  ---> ADC  ADC123IN12
 */

#define STBY PEout(10)

// 获取角度的算法,1:四元数  2:卡尔曼  3:互补滤波
u8 Way_Angle = 1;
// 蓝牙遥控相关的变量
u8 Flag_front, Flag_back, Flag_Left, Flag_Right, Flag_velocity = 2;
// 电机停止标志位 默认停止
u8 Flag_Stop = 1;
// 电机PWM变量
int Motor_Left, Motor_Right;
// 温度变量
int Temperature;
// 左右编码器的脉冲计数
int Encoder_Left, Encoder_Right;
// 电池电压采样相关的变量
int Voltage;
// 平衡倾角 平衡陀螺仪 转向陀螺仪
float Angle_Balance, Gyro_Balance, Gyro_Turn;
float Track_Bias;
// 延时和调参相关变量
int delay_50, delay_flag;
// 蓝牙模式、普通模式标志位
u8 Flag_Blooth = 0, Flag_Normol = 1;
// 获取时钟
RCC_ClocksTypeDef RCC_CLK;
// Z轴加速度计
float Acceleration_Z;
// PID参数(放大100倍)
float Balance_Kp = 20500, Balance_Kd = 110, Velocity_Kp = 16000, Velocity_Ki = 80, Rotate_Turn_Kp = 6000, Rotate_Turn_Kd = 0,Track_Turn_Kp=1400,Track_Turn_Kd=0;
// 车轮速度(mm/s)
float Velocity_Left, Velocity_Right;
// 平衡环PWM变量,速度环PWM变量,转向环PWM变量
int Balance_Pwm, Velocity_Pwm, Rotate_Turn_Pwm,Track_Turn_PWM;

// 位置PID控制变量,速度PID控制变量
int Pos_Pwm, Vel_Pwm;
// 位置PID系数
 float Position_KP = 20, Position_KI = 0.0785, Position_KD = 182.7;
//float Position_KP = 15.5, Position_KI = 0.06, Position_KD = 140;
// 速度PID系数
float Velocity_KP = 30, Velocity_KI = 6, Velocity_KD = 0;
// 位置PID设定目标数
int Target_Position_L = 800, Target_Position_R = 800;
// 速度PID设定目标数
int Target_Velocity_L = 10, Target_Velocity_R = 20;

int main(void)
{
	init_Basic();
	// 初始化设备驱动
	init_drv_Main();
	// 初始化开始声音
	sendLedSignal(LEDSignal_Start1);
	// 初始化控制系统
	init_drv_ControlSystem();
	// 设置初始化已完成
	setInitializationCompleted();
	// 获取时钟
	RCC_GetClocksFreq(&RCC_CLK);
	STBY = 1;
	
	// 进入主循环
	while (1)
	{
// 调试模式的选择
#if Debug
		DataScope();
		delay_ms(50);
#else
		Commulink_Server();
		// 显示
		GUI_Server();
#endif
	}
}

control.c

主控程序

#include "Control.hpp"
#include "drv_ADC.hpp"
#include "drv_Encoder.hpp"
#include "drv_MPU6050.hpp"
#include "drv_Key.hpp"
#include "drv_LED.hpp"
#include "GUI.hpp"
#include <stack>
using namespace std;

#define APB2TIMERCLK 72000000
#define AIN1 PDout(4)
#define AIN2 PCout(12)
#define BIN1 PDout(11)
#define BIN2 PDout(10)
#define LEFTTURN 1
#define RIGHTTURN 2
// 识别到路口至正好行驶到路口上进行旋转的延时时间
int StopDelayTimes = 110;

// 在第几个路口停
int StopCrossNum;
// 识别到的病房号
int IdentifiedNum;
// 是否为近端病房
bool CloseWard;
// 延时计数
int StopDelayCount;
// 标识开始延时
bool ReadyStopFlag;

bool Mark180Flag;

float TargetYaw;
float TargetVelocity = 30;
bool TrackFlag = true;
bool Turn180Flag;
bool TurnLeft90Flag;
bool TurnRight90Flag;
bool CrossFlag;
bool GoBackFlag;
int GoBackCount;
int GoBackTimes = 200;
bool ArrivedFlag;
bool ReturnFlag;
bool JudgingFlag;
// 数字识别转向方向
int TurnSignal;
int JudgingCount;
int JudgingTimes = 400;
int ArrivedNum;
int CrossNum = 0;
int ArrivedCount;
bool PassCrossFlag;
int PassCrossTimes = 400;
int PassCrossCount;
int ArrivedTimes;

int Trace[5];
int TracePtr;
int TraceBit;

extern "C" void TIM8_UP_IRQHandler(void)
{
    if (TIM_GetFlagStatus(TIM8, TIM_FLAG_Update) == SET)
    {
        TIM_ClearITPendingBit(TIM8, TIM_IT_Update);
        if (getInitializationCompleted() == false)
        {
            return;
        }
        Get_Angle(Way_Angle);
        Encoder_Left = -Read_Encoder(4);
        Encoder_Right = -Read_Encoder(3);
        Key();
        if (Flag_Stop)
        {
            Led_Flash(0);
        }
        else
        {
            Led_Flash(100);
        }

        // 路口判断
        if (CrossNum == StopCrossNum && CrossFlag && !ReturnFlag)
        {
            // 不是近端病房就停下来识别,第三个路口也不不识别,直接左转
            if (!CloseWard && StopCrossNum != 3)
            {
                // Flag_Stop = true;
                GoBackFlag = true;
            }
            // 是近端病房就开始停止延时
            else
            {
                ReadyStopFlag = true;
            }
            CrossFlag = false;
        }
        GoBack();
        if (CrossFlag && ReturnFlag)
        {
            if (TracePtr - 1 >= 0)
            {
                ReadyStopFlag = true;
            }
        }
        // openmv长时间识别不出就算直行
        if (JudgingFlag)
        {
            JudgingCount++;
            if (JudgingCount >= JudgingTimes)
            {
                TargetVelocity = 30;
                if (CrossNum == 2)
                {
                    Flag_Stop = false;
                    JudgingFlag = false;
                    JudgingCount = 0;
                    StopCrossNum = 3;
                    PassCrossCount = 0;
                    PassCrossFlag = true;
                    StopDelayTimes = 100;
                }
                if (CrossNum == 4)
                {
                    Flag_Stop = false;
                    JudgingFlag = false;
                    JudgingCount = 0;
                    StopCrossNum = 6;
                    TargetVelocity = 0;
                    Turn180Flag = true;
                    Trace[TracePtr - 1] = RIGHTTURN;
                }
            }
        }
        // 识别路口的延时
        if (PassCrossFlag)
        {
            if (PassCrossCount > PassCrossTimes)
            {
                PassCrossCount = 0;
                PassCrossFlag = false;
            }
            else
            {
                PassCrossCount++;
            }
        }
        // 开始延时
        if (ReadyStopFlag)
        {
            StopDelayCount++;
            if (StopDelayCount == StopDelayTimes)
            {
                // 停车+复位
                TargetVelocity = 0;
                StopDelayCount = 0;
                ReadyStopFlag = false;
                // 转向标识设置
                TrackFlag = false;
                // 近端病房
                if (!ReturnFlag && CloseWard)
                {
                    if (IdentifiedNum == 1)
                        TurnLeft90Flag = true;
                    if (IdentifiedNum == 2)
                        TurnRight90Flag = true;
                }
                else if (!ReturnFlag && !CloseWard)
                {
                    // 判别左转或遇到第3个十字路口
                    if (TurnSignal == 11)
                        TurnLeft90Flag = true;
                    if (TurnSignal == 12)
                        TurnRight90Flag = true;
                    if (StopCrossNum == 3)
                    {
                        // 下一个路口要判断数字
                        StopCrossNum = 4;
                        TurnLeft90Flag = true;
                    }
                    TurnSignal = 0;
                }
                // 回程的转向控制
                else if (ReturnFlag)
                {
                    TracePtr--;
                    if (TracePtr >= 0)
                    {
                        int direction = Trace[TracePtr];
                        if (direction == LEFTTURN)
                            TurnRight90Flag = true;
                        else
                        {
                            TurnLeft90Flag = true;
                        }
                    }
                }
            }
        }
        if (TurnLeft90Flag)
        {
            if (Yaw > 170)
            {
                TargetYaw = -Yaw + 90;
            }
            else
            {
                TargetYaw = Yaw + 90;
            }
            TurnLeft90Flag = false;
            Flag_Left = true;
            if (!ReturnFlag)
            {
                Trace[TracePtr++] = LEFTTURN;
            }
        }
        if (TurnRight90Flag)
        {
            if (Yaw < -170)
            {
                TargetYaw = -Yaw - 90;
            }
            else
            {
                TargetYaw = Yaw - 90;
            }
            TurnRight90Flag = false;
            Flag_Right = true;
            if (!ReturnFlag)
            {
                Trace[TracePtr++] = RIGHTTURN;
            }
        }
        if (Turn180Flag)
        {
            ArrivedCount++;
            if (ArrivedCount == 50)
            {
                TargetVelocity = 0;
                if (Yaw < 0)
                    TargetYaw = Yaw + 180;
                else
                    TargetYaw = Yaw - 180;
                Turn180Flag = false;
                Flag_Left = true;
                ArrivedCount = 0;
                //								Mark180Flag=true;
            }
        }
        if (Flag_Left == 1 || Flag_Right == 1)
        {
            // 转向后恢复前进
            if (myabs(Yaw - TargetYaw) < 15)
            {
                Flag_Left = 0;
                Flag_Right = 0;
                Track_Bias = 0;
                Set_Pwm(0, 0);
                TargetVelocity = 30;
                TrackFlag = true;
                if (ArrivedFlag)
                {
                    TargetVelocity = 0;
                }
                StopDelayTimes = 110;
                if (ArrivedNum == 2)
                {
                    ArrivedNum = 0;
                    Flag_Stop = true;
                }
            }
        }

        Get_Velocity_Form_Encoder(Encoder_Left, Encoder_Right);

        Velocity_Pwm = Velocity(Encoder_Left, Encoder_Right);
        Rotate_Turn_Pwm = RotateTurn(Gyro_Turn);
        Track_Turn_PWM = TrackTurn(Track_Bias);

        Motor_Left = Velocity_Pwm - Rotate_Turn_Pwm + Track_Turn_PWM;
        Motor_Right = Velocity_Pwm + Rotate_Turn_Pwm - Track_Turn_PWM;
        Voltage = Get_battery_volt();
        Motor_Left = PWM_Limit(Motor_Left, 6900, -6900);
        Motor_Right = PWM_Limit(Motor_Right, 6900, -6900);
        if (Pick_Up(Acceleration_Z, Angle_Balance, Encoder_Left, Encoder_Right))
        {
            Flag_Stop = 1;
        }
        if (Put_Down(Angle_Balance, Encoder_Left, Encoder_Right))
        {
            Flag_Stop = 0;
        }
        if (Turn_Off(Angle_Balance, Voltage) == 0)
            Set_Pwm(Motor_Left, Motor_Right);
    }
}
void GoBack()
{
    if (GoBackFlag)
    {
        TargetVelocity = -20;
				TrackFlag=false;
        GoBackCount++;
        if (GoBackCount >= GoBackTimes)
        {
            TargetVelocity = 0;
            GoBackCount = 0;
            GoBackFlag = false;
            JudgingFlag = true;
            PassCrossCount = 0;
            PassCrossFlag = true;
						TrackFlag=true;
        }
    }
}

/*
 * 函数功能:直立PD控制
 * 入口参数:Angle:角度;Gyro:角速度
 * 返回  值:balance:直立控制PWM
 */
int Balance(float Angle, float Gyro)
{
    float angle_bias, gyro_bias;
    int balance;
    angle_bias = Middle_angle - Angle;
    gyro_bias = 0 - Gyro;
    balance = -Balance_Kp / 100 * angle_bias - gyro_bias * Balance_Kd / 100;
    return balance;
}
/*
 * 函数功能:速度控制PWM
 * 入口参数:encoder_left:左轮编码器读数;encoder_right:右轮编码器读数
 * 返回  值:速度控制PWM
 */
int Velocity(int encoder_left, int encoder_right)
{
    static float velocity, encoder_least, encoder_bias;
    static float encoder_integral, target_velocity;
    encoder_least = TargetVelocity - (encoder_left + encoder_right);
    // 低通滤波
    encoder_bias *= 0.84;
    encoder_bias += encoder_least * 0.16;
    encoder_integral += encoder_bias;
    if (encoder_integral > 10000)
    {
        encoder_integral = 10000;
    }
    else if (encoder_integral < -10000)
    {
        encoder_integral = -10000;
    }
    velocity = -encoder_bias * Velocity_Kp / 100 - encoder_integral * Velocity_Ki / 100; // seems have problems
    if (Turn_Off(Angle_Balance, Voltage) == 1 || Flag_Stop == 1)
        encoder_integral = 0;
    return velocity;
}
/*
 * 函数功能:转向控制
 * 入口参数:Z轴陀螺仪
 * 返回  值:转向控制PWM
 */
int RotateTurn(float gyro)
{
    static float Turn_Target, turn, Turn_Amplitude = 54;
    float Kp = Rotate_Turn_Kp, Kd;
    if (1 == Flag_Left)
    {
        Turn_Target = -Turn_Amplitude / Flag_velocity;
    }
    else if (1 == Flag_Right)

    {
        Turn_Target = Turn_Amplitude / Flag_velocity;
    }
    else
    {
        Turn_Target = 0;
    }
    if (1 == Flag_front || 1 == Flag_back)
        Kd = Rotate_Turn_Kd;
    else
        Kd = 0;

    turn = Turn_Target * Kp / 100 + gyro * Kd / 100;
    return turn;
}

int TrackTurn(float bias)
{
    if (TrackFlag)
    {
        static float general_bias, turn, last_bias;
        general_bias = 0.84 * bias + 0.16 * last_bias;
        last_bias = bias;

        turn = general_bias * Track_Turn_Kp / 100;

        return turn;
    }
    else
    {
        return 0;
    }
}

/*
 * 函数功能:赋值给PWM寄存器
 * 入口参数:左轮PWM、右轮PWM
 * 返回  值:无
 */
void Set_Pwm(int motor_left, int motor_right)
{
    int pwmb, pwma;
    if (motor_left > 0)
    {
        BIN1 = 0;
        BIN2 = 1;
    }
    else
    {
        BIN1 = 1;
        BIN2 = 0;
    }
    pwmb = myabs(motor_left);
    TIM_SetCompare3(TIM1, pwmb);
    if (motor_right > 0)
    {
        AIN1 = 0;
        AIN2 = 1;
    }
    else
    {
        AIN1 = 1;
        AIN2 = 0;
    }
    pwma = myabs(motor_right);
    TIM_SetCompare4(TIM1, pwma);
}
/*
 * 函数功能:限制PWM赋值
 * 入口参数:IN:输入参数  max:限幅最大值  min:限幅最小值
 * 返回  值:限幅后的值
 */
int PWM_Limit(int IN, int max, int min)
{
    int OUT;
    if (IN > max)
    {
        OUT = max;
    }
    else if (IN < min)
    {
        OUT = min;
    }
    else
    {
        OUT = IN;
    }
    return OUT;
}
/*
 * 函数功能:按键修改小车运行状态
 * 入口参数:无
 * 返回  值:无
 */
void Key(void)
{
    u8 temp;
    temp = click_N_Double(50);
    if (temp == 1)
    {
        Track_Bias = 0;
        if (ArrivedFlag)
        {
            ArrivedFlag = false;
            TargetVelocity = 30;
        }
        else
        {
            TurnRight90Flag = TurnLeft90Flag = 0;
            Flag_Right = false;
            Flag_Left = false;
            CrossNum = 0;
            Flag_Stop = !Flag_Stop;
        }
    }
}
/*
 * 函数功能:异常关闭电机
 * 入口参数:angle:小车倾角;voltage:电压
 * 返回  值:1:异常  0:正常
 */
u8 Turn_Off(float angle, int voltage)
{
    u8 temp;
    if (angle < -40 || angle > 40 || Flag_Stop == 1 || voltage < 1170)
    {
        temp = 1;
        AIN1 = 0;
        AIN2 = 0;
        BIN1 = 0;
        BIN2 = 0;
    }
    else
    {
        temp = 0;
    }
    return temp;
}

/*
 * 函数功能:绝对值函数
 * 入口参数:a:需要计算绝对值的数
 * 返回  值:无符号整型
 */
int myabs(int a)
{
    int temp;
    if (a < 0)
    {
        temp = -a;
    }
    else
    {
        temp = a;
    }
    return temp;
}
/*
 * 函数功能:检测小车是否被拿起
 * 入口参数:Acceleration:z轴加速度;Angle:平衡的角度;encoder_left:左编码器计数;encoder_right:右编码器计数
 * 返回  值:1:小车被拿起  0:小车未被拿起
 */
int Pick_Up(float Acceleration, float Angle, int encoder_left, int encoder_right)
{
    static u16 flag, count0, count1, count2;
    if (flag == 0)
    {
        // 静止
        if (myabs(encoder_left) + myabs(encoder_right) < 30)
        {
            count0++;
        }
        else
        {
            count0 = 0;
        }
        if (count0 > 10)
        {
            flag = 1, count0 = 0;
        }
    }
    // 在0度附近被拿起
    if (flag == 1)
    {
        if (++count1 > 200)
        {
            count1 = 0;
            flag = 0;
        }
        if (Acceleration > 26000 && (Angle > (-20 + Middle_angle) && Angle < (20 + Middle_angle)))
        {
            flag = 2;
        }
    }
    // 正反馈达到最大转速
    if (flag == 2)
    {
        if (++count2 > 200)
        {
            count2 = 0;
            flag = 0;
        }
        if (myabs(encoder_left + encoder_right) > 70)
        {
            flag = 0;
            return 1;
        }
    }
    return 0;
}
/*
 * 函数功能:检测小车是否被放下
 * 入口参数:平衡角度;左编码器读数;右编码器读数
 * 返回  值:1:小车放下   0:小车未放下
 */
int Put_Down(float Angle, int encoder_left, int encoder_right)
{
    static int flag, count;
    if (Flag_Stop == 0)
    {
        return 0;
    }
    if (flag == 0)
    {
        if (Angle > (-10 + Middle_angle) && Angle > (10 + Middle_angle) && encoder_right == 0 && encoder_left == 0)
        {
            flag = 1;
        }
    }
    if (flag == 1)
    {
        if (++count > 50)
        {
            count = 0;
            flag = 0;
        }
        if (encoder_left > 3 && encoder_right > 3 && encoder_left < 40 && encoder_right < 40)
        {
            flag = 0;
            count = 0;
            return 1;
        }
    }
    return 0;
}
/*
 * 函数功能:编码器读数转换为速度(mm/s)
 * 入口参数:无
 * 返回  值:无
 */
void Get_Velocity_Form_Encoder(int encoder_left, int encoder_right)
{
    float rotation_speed_L, rotation_speed_R;
    rotation_speed_L = encoder_left * Control_Frequency / EncoderMultiples / Reduction_Ratio / Encoder_precision;
    Velocity_Left = rotation_speed_L * PI * Diameter_67;
    rotation_speed_R = encoder_right * Control_Frequency / EncoderMultiples / Reduction_Ratio / Encoder_precision;
    Velocity_Right = rotation_speed_R * PI * Diameter_67;
}
/*
 * 函数功能:选择小车运行模式
 * 入口参数:encoder_left:左编码器读数  encoder_right:右编码器读数
 * 返回  值:无
 */
void Choose(int encoder_left, int encoder_right);

void init_drv_ControlSystem()
{
    RCC->APB2ENR |= (1 << 13);
    // 需要问一下chatgpt
    //  os_delay(1e-2);
    TIM8->PSC = (APB2TIMERCLK / 1000000) - 1;
    TIM8->ARR = 1e6 / 200 - 1;
    TIM8->DIER = 1 << 0;
    TIM8->CR1 = 1 << 0;
    NVIC_SetPriority(TIM8_UP_IRQn, 3);
    NVIC_EnableIRQ(TIM8_UP_IRQn);
}

int Set_Postion_PID_L(int position, int target)
{
    static float bias, pwm, integral_bias, last_bias;
    bias = target - position;
    integral_bias += bias;
    if (integral_bias > 3000)
    {
        integral_bias = 3000;
    }
    else if (integral_bias < -3000)
    {
        integral_bias = -3000;
    }
    pwm = Position_KP * bias + Position_KI * integral_bias + Position_KD * (bias - last_bias);
    last_bias = bias;
    return pwm;
}

int Set_Postion_PID_R(int position, int target)
{
    static float bias, pwm, integral_bias, last_bias;
    bias = target - position;
    integral_bias += bias;
    if (integral_bias > 3000)
    {
        integral_bias = 3000;
    }
    else if (integral_bias < -3000)
    {
        integral_bias = -3000;
    }
    pwm = Position_KP * bias + Position_KI * integral_bias + Position_KD * (bias - last_bias);
    last_bias = bias;
    return pwm;
}
// 增量式PID:P是D,I是P;
int Set_Incremental_PI_L(int incremental, int target)
{
    static float bias, pwm, last_bias;
    bias = target - incremental;
    pwm += Velocity_KP * (bias - last_bias) + Velocity_KI * bias;
    last_bias = bias;
    return pwm;
}
int Set_Incremental_PI_R(int incremental, int target)
{
    static float bias, pwm, last_bias;
    bias = target - incremental;
    pwm += Velocity_KP * (bias - last_bias) + Velocity_KI * bias;
    last_bias = bias;
    return pwm;
}

其他

可在guthub中找到(*^_^*)