因为电脑中只装了IAR,所以本次编译环境就只能是IAR,所用软件版本是9.32.1。
本次仿真为,纯手写代码,不用任何库,包括启动文件也是手写。
首先是启动文件,该文件是汇编文件,命名为start.s,只写最基本的部分,可以正常启动就行,不需要完整的中断向量表。
MODULE ?cstartupSECTION CSTACK:DATA:NOROOT(3)SECTION .intvec:CODE:NOROOT(2)EXTERN __iar_program_startEXTERN SysTick_HandlerPUBLIC __vector_tableDATA__vector_tableDCD sfe(CSTACK)DCD Reset_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD NMI_HandlerDCD SysTick_HandlerTHUMBPUBWEAK Reset_HandlerSECTION .text:CODE:REORDER:NOROOT(2)
Reset_HandlerLDR R0,=__iar_program_startBX R0PUBWEAK NMI_HandlerSECTION .text:CODE:REORDER:NOROOT(1)
NMI_HandlerB NMI_HandlerEND
接下来是main.c文件,因为只是仿真调速,代码量不多,所以就都写在同一个文件中了。
首先是Systick结构体定义以及定义Systick指针并指向Systick指向的位置,以及初始化和Systick中断的实现,方便计时功能。
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))
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的结构体的定义。
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的初始化
void PBH_Out_Init(uint8_t pin)
{uint32_t reg = GPIOB->CRH;reg &= ~(0xF << ((pin & 0x07) << 2));reg |= (0x02 << ((pin & 0x07) << 2));//推挽输出 20MGPIOB->CRH = reg;
}void PBL_Out_Init(uint8_t pin)
{uint32_t reg = GPIOB->CRL;reg &= ~(0xF << ((pin & 0x07) << 2));reg |= (0x02 << ((pin & 0x07) << 2));//推挽输出 20MGPIOB->CRL = reg;
}void PB_Out_Init(uint8_t pin)
{if(pin & 0x08)PBH_Out_Init(pin);elsePBL_Out_Init(pin);
}void PB_Out(uint8_t pin,uint8_t State)
{if(State)GPIOB->BSRR = 0x01 << (pin & 0x0F);elseGPIOB->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);elsePBL_In_Init(pin);
}uint8_t PB_In(uint8_t pin)
{return (GPIOB->IDR >> pin) & 0x01;
}
需要写一个简单的PWM输出,用于给电机调速,调用周期是10kHz,PWM的输入范围限制在0~99,共100个数。
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;
}
这个系统需要显示,为了简单,这里直接使用共阳数码管。
void SEG_Init(void)
{GPIOA->CRL = 0x22222222;GPIOA->CRH = 0x22222222;GPIOA->ODR = 0x0000FF00;
}const unsigned char displayCodeArray[16] =
{0xC0,//00xF9,//10xA4,//20xB0,//30x99,//40x92,//50x82,//60xF8,//70x80,//80x90,//90x88,//A0x83,//B0xC6,//C0xA1,//D0x86,//E0x8E,//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;}
}
仿真过程中需要输入目标速度,所以用了三个独立按键,分别是速度+,速度-,速度确认,仿真环境就不用做防抖处理了。
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倍频计数了。
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;//+1EncodeNum ++;}if((temp & 0x06) == EncodeCode[(EncodeNum - 1) & 0x03]){ret = 2;//-1EncodeNum --;}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了。
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函数了,以及函数声明了。
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 KhzPWM_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()){}}
}
最后在贴一张仿真图。