因为电脑中只装了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())
{
}
}
}
最后在贴一张仿真图。