写一个基于DSPIC系列单片机的位置单闭环PID控制有刷直流电机,且电机不带编码器的完整程序
时间: 2023-07-16 09:14:09 浏览: 227
以下是一个基于DSPIC33FJ32MC204单片机的位置单闭环PID控制有刷直流电机的程序,其中电机不带编码器:
```c
#include "p33FJ32MC204.h"
#define FCY 40000000UL // 定义主频为40MHz
#include <libpic30.h> // 使用__delay_ms()函数
// 定义一些常量
#define PWM_FREQ 10000 // 定义PWM频率为10kHz
#define MAX_PWM_DUTY 4000 // 定义PWM最大占空比
#define KP 10.0 // 定义比例系数
#define KI 1.0 // 定义积分系数
#define KD 1.0 // 定义微分系数
// 定义全局变量
volatile int16_t target_pos = 0; // 目标位置
volatile int16_t current_pos = 0; // 当前位置
volatile int16_t last_pos = 0; // 上一次的位置
volatile int16_t error = 0; // 误差
volatile int16_t last_error = 0; // 上一次的误差
volatile int16_t pid_output = 0; // PID输出
volatile int16_t pwm_duty = 0; // PWM占空比
// 定义函数原型
void init_peripheral();
void init_pwm();
void init_adc();
void init_timer();
void init_pid();
void set_pwm_duty(int16_t duty);
int16_t read_adc();
void update_pid();
// 主函数
int main(void)
{
init_peripheral(); // 初始化外设
init_pwm(); // 初始化PWM模块
init_adc(); // 初始化ADC模块
init_timer(); // 初始化定时器
init_pid(); // 初始化PID参数
while (1) {
current_pos = read_adc(); // 读取当前位置
update_pid(); // 更新PID参数
set_pwm_duty(pwm_duty); // 设置PWM占空比
__delay_ms(1); // 等待1毫秒
}
return 0;
}
// 初始化外设
void init_peripheral()
{
// 配置时钟
CLKDIVbits.PLLPOST = 0; // PLL分频器后分频系数为2
PLLFBD = 38; // PLL倍频系数为40
CLKDIVbits.PLLPRE = 0; // PLL分频器前分频系数为2
while (OSCCONbits.LOCK != 1); // 等待PLL稳定
// 配置IO口
TRISBbits.TRISB0 = 1; // 将RB0设置为输入
ANSBbits.ANSB0 = 1; // 将RB0设置为模拟输入
TRISBbits.TRISB1 = 0; // 将RB1设置为输出
LATBbits.LATB1 = 0; // 将RB1输出低电平
}
// 初始化PWM模块
void init_pwm()
{
// 配置IO口
TRISBbits.TRISB2 = 0; // 将RB2设置为输出
RPOR1bits.RP2R = 18; // 将RP2映射为OC1输出
// 配置PWM模块
PTCONbits.PTEN = 0; // 禁止PWM模块
PTCONbits.PTMOD = 0; // PWM模式为独立模式
PTCONbits.PTOPS = 0; // PWM输出极性为高电平有效
PTCONbits.PTCKPS = 0; // PWM时钟分频系数为1
PTCONbits.PTSIDL = 0; // PWM模块在空闲状态下继续工作
PWMCON1bits.PMOD1 = 0; // PWM1模块为标准模式
PWMCON1bits.PEN1L = 1; // PWM1L输出使能
PTPER = FCY / (PWM_FREQ * 2) - 1; // PWM周期为10kHz
PWMCON2bits.IUE = 1; // 立即更新PWM寄存器
PTCONbits.PTEN = 1; // 启用PWM模块
}
// 初始化ADC模块
void init_adc()
{
// 配置ADC模块
AD1CON1bits.ADON = 0; // 禁止ADC模块
AD1CON1bits.AD12B = 1; // ADC模块为12位模式
AD1CON1bits.FORM = 0; // ADC结果为整数
AD1CON1bits.SSRC = 0b111; // 自动采样触发
AD1CON2bits.VCFG = 0; // 参考电压为AVDD和AVSS
AD1CON2bits.CSCNA = 1; // 扫描输入通道
AD1CON2bits.SMPI = 0; // 中断采样结束
AD1CON3bits.ADRC = 0; // ADC时钟源为系统时钟
AD1CON3bits.ADCS = 63; // ADC时钟分频系数为64
AD1CSSLbits.CSS0 = 1; // 扫描RB0通道
AD1CON1bits.ADON = 1; // 启用ADC模块
// 等待ADC模块稳定
__delay_us(10);
}
// 初始化定时器
void init_timer()
{
// 配置定时器
T2CONbits.TON = 0; // 禁止定时器
T2CONbits.TCKPS = 0; // 定时器时钟分频系数为1
T2CONbits.TCS = 0; // 定时器时钟源为内部时钟
TMR2 = 0; // 清零定时器计数器
PR2 = FCY / 1000 - 1; // 定时器周期为1ms
IFS0bits.T2IF = 0; // 清除定时器中断标志位
IEC0bits.T2IE = 1; // 使能定时器中断
T2CONbits.TON = 1; // 启用定时器
}
// 初始化PID参数
void init_pid()
{
target_pos = 0;
current_pos = read_adc();
last_pos = current_pos;
error = 0;
last_error = 0;
pid_output = 0;
pwm_duty = 0;
}
// 设置PWM占空比
void set_pwm_duty(int16_t duty)
{
if (duty > MAX_PWM_DUTY) {
duty = MAX_PWM_DUTY;
} else if (duty < -MAX_PWM_DUTY) {
duty = -MAX_PWM_DUTY;
}
if (duty >= 0) {
PDC1 = duty;
LATBbits.LATB1 = 0;
} else {
PDC1 = -duty;
LATBbits.LATB1 = 1;
}
}
// 读取当前位置
int16_t read_adc()
{
AD1CON1bits.ASAM = 1; // 自动采样开始
while (!IFS0bits.AD1IF); // 等待采样完成
IFS0bits.AD1IF = 0; // 清除ADC中断标志位
return ADC1BUF0; // 返回采样结果
}
// 更新PID参数
void update_pid()
{
error = target_pos - current_pos;
pid_output = KP * error + KI * (error + last_error) + KD * (error - last_error);
pwm_duty += pid_output;
last_error = error;
last_pos = current_pos;
}
// 定时器中断服务函数
void __attribute__((interrupt, no_auto_psv)) _T2Interrupt(void)
{
IFS0bits.T2IF = 0; // 清除定时器中断标志位
}
```
程序的关键部分是`update_pid()`函数,它根据当前位置和目标位置计算出误差,然后根据比例、积分和微分系数计算出PID输出。最后将PID输出加到PWM占空比上,并更新上一次的误差和位置。在`main()`函数中,我们不断读取当前位置,更新PID参数,并设置PWM占空比。因为电机没有编码器,所以我们使用ADC模块来读取电机位置。定时器中断用于控制程序运行频率,这里设置为1ms一次。
阅读全文