在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())
        {
            
        }
    }
}

最后在贴一张仿真图。

相关推荐
杰克逊的日记9 天前
MCU编程
单片机·嵌入式硬件
Python小老六9 天前
单片机测ntc热敏电阻的几种方法(软件)
数据库·单片机·嵌入式硬件
懒惰的bit9 天前
STM32F103C8T6 学习笔记摘要(四)
笔记·stm32·学习
HX科技9 天前
STM32给FPGA的外挂FLASH进行升级
stm32·嵌入式硬件·fpga开发·flash·fpga升级
Suagrhaha9 天前
驱动入门的进一步深入
linux·嵌入式硬件·驱动
国科安芯9 天前
基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
嵌入式硬件·硬件架构·硬件工程
Li Zi9 天前
STM32 ADC(DMA)双缓冲采集+串口USART(DMA)直接传输12位原始数据到上位机显示并保存WAV格式音频文件 收藏住绝对实用!!!
经验分享·stm32·单片机·嵌入式硬件
进击的程序汪9 天前
触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
linux·网络·嵌入式硬件
damo王9 天前
Zephyr 系统深入解析:SoC 支持包结构与中断调度器调优实践
单片机·嵌入式硬件·zephyr
逼子格9 天前
硬件工程师笔试面试高频考点汇总——(2025版)
单片机·嵌入式硬件·面试·硬件工程·硬件工程师·硬件工程师真题·硬件工程师面试