在Proteus软件仿真STM32F103寄存器方式PID调速电机

因为电脑中只装了IAR,所以本次编译环境就只能是IAR,所用软件版本是9.32.1。

本次仿真为,纯手写代码,不用任何库,包括启动文件也是手写。

首先是启动文件,该文件是汇编文件,命名为start.s,只写最基本的部分,可以正常启动就行,不需要完整的中断向量表。

    MODULE      ?cstartup
    
    SECTION     CSTACK:DATA:NOROOT(3)
    
    SECTION     .intvec:CODE:NOROOT(2)
    
    EXTERN      __iar_program_start
    EXTERN      SysTick_Handler
    PUBLIC      __vector_table
    
    DATA
    
__vector_table
    DCD         sfe(CSTACK)
    DCD         Reset_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         NMI_Handler
    DCD         SysTick_Handler
    
    THUMB
    
    PUBWEAK Reset_Handler
    SECTION .text:CODE:REORDER:NOROOT(2)
Reset_Handler
    LDR         R0,=__iar_program_start
    BX          R0
    
    PUBWEAK NMI_Handler
    SECTION .text:CODE:REORDER:NOROOT(1)
NMI_Handler
    B           NMI_Handler
    
    END

接下来是main.c文件,因为只是仿真调速,代码量不多,所以就都写在同一个文件中了。

首先是Systick结构体定义以及定义Systick指针并指向Systick指向的位置,以及初始化和Systick中断的实现,方便计时功能。

cpp 复制代码
typedef struct
{
    volatile uint32_t CTRL;
    volatile uint32_t LOAD;
    volatile uint32_t VAL;
    volatile uint32_t CALIB;
}SysTick_Def;

#define SysTick_BASE    0xE000E010

#define SysTick         ((SysTick_Def *)(SysTick_BASE))
cpp 复制代码
void SysTick_Init(void)
{
    SysTick->LOAD = 800-1;
    SysTick->VAL = 0;
    SysTick->CTRL = 0x07;
}

static volatile uint32_t TickCount = 0;

void SysTick_Handler(void)
{
    TickCount ++;
    if(TickCount >= 10001)
    {
        TickCount = 1;
    }
}

uint32_t Get_SysTickCount(void)
{
    return TickCount;
}

然后是时钟管理单元RCC和GPIO的结构体的定义。

cpp 复制代码
typedef struct
{
  volatile uint32_t CR;
  volatile uint32_t CFCR;
  volatile uint32_t CIR;
  volatile uint32_t APB2RSTR;
  volatile uint32_t APB1RSTR;
  volatile uint32_t AHBENR;
  volatile uint32_t APB2ENR;
  volatile uint32_t APB1ENR;
  volatile uint32_t BDCR;
  volatile uint32_t CSR;
}RCC_TypeDef;

typedef struct
{
   volatile uint32_t CRL;
   volatile uint32_t CRH;
   volatile uint32_t IDR;
   volatile uint32_t ODR;
   volatile uint32_t BSRR;
   volatile uint32_t BRR;
   volatile uint32_t LCKR;
}GPIO_TypeDef;

#define RCC_BASE        0x40021000
#define GPIOA_BASE      0x40010800
#define GPIOB_BASE      0x40010C00
#define GPIOC_BASE      0x40011000
#define GPIOD_BASE      0x40011400
#define GPIOE_BASE      0x40011800

#define RCC             ((RCC_TypeDef *)(RCC_BASE))
#define GPIOA           ((GPIO_TypeDef *)(GPIOA_BASE))
#define GPIOB           ((GPIO_TypeDef *)(GPIOB_BASE))
#define GPIOC           ((GPIO_TypeDef *)(GPIOC_BASE))
#define GPIOD           ((GPIO_TypeDef *)(GPIOD_BASE))
#define GPIOE           ((GPIO_TypeDef *)(GPIOE_BASE))

接着是GPIOB的初始化

cpp 复制代码
void PBH_Out_Init(uint8_t pin)
{
    uint32_t reg = GPIOB->CRH;
    reg &= ~(0xF << ((pin & 0x07) << 2));
    reg |= (0x02 << ((pin & 0x07) << 2));//推挽输出 20M
    GPIOB->CRH = reg;
}

void PBL_Out_Init(uint8_t pin)
{
    uint32_t reg = GPIOB->CRL;
    reg &= ~(0xF << ((pin & 0x07) << 2));
    reg |= (0x02 << ((pin & 0x07) << 2));//推挽输出 20M
    GPIOB->CRL = reg;
}

void PB_Out_Init(uint8_t pin)
{
    if(pin & 0x08)
        PBH_Out_Init(pin);
    else
        PBL_Out_Init(pin);
}

void PB_Out(uint8_t pin,uint8_t State)
{
    if(State)
        GPIOB->BSRR = 0x01 << (pin & 0x0F);
    else
        GPIOB->BRR = 0x01 << (pin & 0x0F);
}

void PBH_In_Init(uint8_t pin)
{
    uint32_t reg = GPIOB->CRH;
    reg &= ~(0xF << ((pin & 0x07) << 2));
    reg |= (0x08 << ((pin & 0x07) << 2));//上下拉输入
    GPIOB->CRH = reg;
}

void PBL_In_Init(uint8_t pin)
{
    uint32_t reg = GPIOB->CRL;
    reg &= ~(0xF << ((pin & 0x07) << 2));
    reg |= (0x08 << ((pin & 0x07) << 2));//上下拉输入
    GPIOB->CRL = reg;
}

void PB_In_Init(uint8_t pin)
{
    if(pin & 0x08)
        PBH_In_Init(pin);
    else
        PBL_In_Init(pin);
}

uint8_t  PB_In(uint8_t pin)
{
    return (GPIOB->IDR >> pin) & 0x01;
}

需要写一个简单的PWM输出,用于给电机调速,调用周期是10kHz,PWM的输入范围限制在0~99,共100个数。

cpp 复制代码
uint8_t Pwm_num = 10;

void PWM_Out(void)//rate:10Khz
{
    static uint8_t count = 0;
    if(count == 0)
        PB_Out(12,1);
    if(count == Pwm_num)
        PB_Out(12,0);
    
    count ++;
    if(count ==100)
        count = 0;
}

这个系统需要显示,为了简单,这里直接使用共阳数码管。

cpp 复制代码
void SEG_Init(void)
{
    GPIOA->CRL = 0x22222222;
    GPIOA->CRH = 0x22222222;
    GPIOA->ODR = 0x0000FF00;
}

const unsigned char displayCodeArray[16] =
{
	0xC0,//0
	0xF9,//1
	0xA4,//2
	0xB0,//3
	0x99,//4
	0x92,//5
	0x82,//6
	0xF8,//7
	0x80,//8
	0x90,//9
    0x88,//A
    0x83,//B
    0xC6,//C
    0xA1,//D
    0x86,//E
    0x8E,//F
};

uint8_t dispalyArray[8] = {0xFF};

void SEG_Step(void)
{
    static uint8_t dp = 0;
    uint16_t temp = dispalyArray[7 - dp];
    temp |= (1 << (dp + 8));
    GPIOA->ODR = temp;
    dp ++;
    dp &= 0x07;
}

void TargetSSpeed_Write(uint16_t speed)
{
    dispalyArray[0] = 0xFF;
    dispalyArray[1] = 0xFF;
    dispalyArray[2] = 0xFF;
    dispalyArray[3] = 0xFF;
    for(uint32_t i = 0;i < 4;i ++)
    {
        dispalyArray[i] = displayCodeArray[speed % 10];
        speed /= 10;
        if(speed == 0)
            break;
    }
}

void CurrentSpeed_Write(uint16_t speed)
{
    dispalyArray[4] = 0xFF;
    dispalyArray[5] = 0xFF;
    dispalyArray[6] = 0xFF;
    dispalyArray[7] = 0xFF;
    for(uint32_t i = 0;i < 4;i ++)
    {
        dispalyArray[i + 4] = displayCodeArray[speed % 10];
        speed /= 10;
        if(speed == 0)
            break;
    }
}

仿真过程中需要输入目标速度,所以用了三个独立按键,分别是速度+,速度-,速度确认,仿真环境就不用做防抖处理了。

cpp 复制代码
void Key_Init(void)
{
    PB_In_Init(13);
    PB_In_Init(14);
    PB_In_Init(15);
    PB_Out(13,1);
    PB_Out(14,1);
    PB_Out(15,1);
}

uint8_t Key_read(void)
{
    static uint8_t lastKey;
    uint8_t temp = (GPIOB->IDR >> 13) & 0x07;
    if(lastKey == temp)return 0;
    lastKey = temp;
    if(temp == 6)return 1;
    if(temp == 5)return 2;
    if(temp == 3)return 3;
    return 0;
}

uint16_t TargetSpeedDispalyValue = 50;
uint32_t TargetSpeed = 500;

void TargetSpeet_read(void)
{
    uint8_t key = Key_read();
    //if(key == 0)return;
    if(key == 1)TargetSpeedDispalyValue++;
    if(key == 2)TargetSpeedDispalyValue--;
    TargetSSpeed_Write(TargetSpeedDispalyValue);
    if(key == 3)
    {
        TargetSpeed = TargetSpeedDispalyValue * 10;
    }
}

要想把电机调到指定的速度,就需要读取电机的编码器,并计算速度。这个编码器是AB增量式编码器,就直接按4倍频计数了。

cpp 复制代码
const uint8_t EncodeCode[4] = {0x06,0x02,0x00,0x04};
static uint8_t EncodeNum;

void Encode_Init(void)
{
    PB_In_Init(0);
    PB_In_Init(1);
    PB_In_Init(2);
    PB_Out(0,1);
    PB_Out(1,1);
    PB_Out(2,1);
    uint8_t temp = GPIOB->IDR & 0x06;
    for(uint8_t i = 0;i < 4;i ++)
    {
        if(temp == EncodeCode[i])
        {
            EncodeNum = i;
            return;
        }
    }
}

uint8_t Encode_read(void)
{
    uint8_t ret = 0;
    uint8_t temp = GPIOB->IDR & 0x07;
    
    if((temp & 0x06) == EncodeCode[EncodeNum])return 0;//没有检测到变化
    
    if((temp & 0x06) == EncodeCode[(EncodeNum + 1) & 0x03])
    {
        ret = 1;//+1
        EncodeNum ++;
    }
    if((temp & 0x06) == EncodeCode[(EncodeNum - 1) & 0x03])
    {
        ret = 2;//-1
        EncodeNum --;
    }
    EncodeNum &= 0x03;
    
    if(temp & 0x01)ret += 8;//零点
    
    return ret;
}

static int32_t EncodeValue = 0;

void EncodeValueAdd(void)
{
    uint8_t temp = Encode_read();
    if(temp & 0x01)EncodeValue ++;
    if(temp & 0x02)EncodeValue --;
}

int CurrentSpeed = 0;

void MotoSpeed(void)//rate 100hz
{
    static int32_t EncodeValueLast = 0,count = 0;
    int32_t temp = EncodeValue - EncodeValueLast;
    EncodeValueLast = EncodeValue;
    static int32_t speed[8] = {0};
    speed[count ++] = temp * 60;//speed = temp * 10 / 100 * 60 * 10;
    count &= 0x07;
    CurrentSpeed = 0;
    for(uint8_t i = 0;i < 8;i ++)CurrentSpeed += speed[i];
    CurrentSpeed /= 8;
}

void dispalySpeed(void)
{
    CurrentSpeed_Write(CurrentSpeed / 10);
}

最后需要实现的是PID控制代码了,当然这个是位置式PID了。

cpp 复制代码
typedef struct
{
    float Kp;
    float Ki;
    float Kd;
    int32_t integral;
    int32_t e1;
    int32_t e2;
}Pid_Def;

float Pid_Calculate(Pid_Def* PID,int32_t TargetValue,int32_t CurrentValue)
{
    int32_t e0 = TargetValue - CurrentValue;
    PID->integral += e0;
    float Output = (PID->Kp * e0)
                   + (PID->Ki * PID->integral)
                   + (PID->Kd * (e0 - PID->e1));
    PID->e1 = e0;
    return Output;
}

Pid_Def Pid = {0.19,0.01,0.01,0,0,0};

void PwmOutValue_Calculate(void)
{
    float value = Pid_Calculate(&Pid,TargetSpeed,CurrentSpeed);
    //value += Pwm_num;
    value = value > 99 ? 99 : value;
    Pwm_num = (uint8_t)(value < 0 ? 0 : value);
}

最后是main函数了,以及函数声明了。

cpp 复制代码
void SysTick_Init(void);
uint32_t Get_SysTickCount(void);
void PB_Out_Init(uint8_t pin);
void PB_Out(uint8_t pin,uint8_t State);
void PWM_Out(void);
void SEG_Init(void);
void SEG_Step(void);
void TargetSSpeed_Write(uint16_t speed);
void CurrentSpeed_Write(uint16_t speed);
void Key_Init(void);
void TargetSpeet_read(void);
void Encode_Init(void);
void EncodeValueAdd(void);
void MotoSpeed(void);//rate 200hz
void dispalySpeed(void);
void PwmOutValue_Calculate(void);

void main()
{
    SysTick_Init();
    RCC->APB2ENR = 0xFFFFFFFF;
    Key_Init();
    SEG_Init();
    Encode_Init();
    PB_Out_Init(12);
    PB_Out(12,1);
    PB_Out_Init(9);
    PB_Out(9,1);
    PB_Out_Init(10);
    PB_Out(10,0);
    while(1)
    {
        uint32_t tick = Get_SysTickCount();//10 Khz
        PWM_Out();
        if((tick >= 5) && (tick % 5 == 0))//0.5ms
        {
            EncodeValueAdd();
        }
        if((tick >= 10) && (tick % 10 == 0))//1ms
        {
        }
        if((tick >= 20) && (tick % 20 == 0))//2ms
        {
            SEG_Step();
        }
        if((tick >= 100) && (tick % 100 == 0))//10ms
        {
        }
        if((tick >= 200) && (tick % 200 == 0))//20ms
        {
        }
        if((tick >= 500) && (tick % 500 == 0))//50ms
        {
            TargetSpeet_read();
        }
        if((tick >= 1000) && (tick % 1000 == 0))//100ms
        {
            MotoSpeed();
        }
        if((tick >= 2000) && (tick % 2000 == 0))//200ms
        {
            PwmOutValue_Calculate();
            dispalySpeed();
        }
        while(tick == Get_SysTickCount())
        {
            
        }
    }
}

最后在贴一张仿真图。

相关推荐
高工智能汽车11 分钟前
洗牌!汽车MCU进入「大混战时代」,谁能够 “伺机突围”?
单片机·嵌入式硬件·汽车
嵌入式小强工作室3 小时前
stm32 iic电阻怎么选
stm32·单片机·嵌入式硬件
jinkang_zhao3 小时前
STM32, GD32 cubemx CAN 低速率125kbps 报文丢失,解决了
stm32·单片机·嵌入式硬件
whaosoft-1434 小时前
51c嵌入式~合集3
嵌入式硬件
南城花随雪。4 小时前
单片机:实现蜂鸣器数码管的显示(附带源码)
单片机·嵌入式硬件
Be Legendary-CGK4 小时前
【硬件接口】I2C总线接口
单片机·嵌入式硬件·硬件工程
六壹班班长5 小时前
STM外设介绍2(Timer)
stm32·单片机·嵌入式硬件
哈士奇上蔚6 小时前
STM32使用SFUD库驱动W25Q64
stm32·嵌入式硬件
Peter_Gao_6 小时前
单片机STM32、GD32、ESP32开发板的差异和应用场景
stm32·单片机·嵌入式硬件