📄 main.c
字号:
// edited by matthew, May. 30, 2007
/* MCU2的程序
// 功能:
// 1. 接收上位机的数据:方向、速度、位移、Kp、Ki、Kd
// 2. 进行闭环控制(PID控制)
// 3. 向MCU1发送闭环控制后的PWM占空比(脉宽);向上位机回传当前速度、位移,用图形显示
*/
#include <iom8v.h>
#include <macros.h>
#include "v_pid.h" // 进行PID控制的函数在此定义
//#define FORWARD 0x01
//#define BACKWARD 0x00
#define SendStart 0x01 // 发送开始
#define SendOver 0x00 // 发送结束
#define LENGTH 7 // 下位机发送位数为7+1
#define MIDDISTANCE 65536L // 初始位移
#define DeadError 50L // 死区范围,当目标位置小于DeadError时,电机停转。
unsigned char VelSendFlag=SendOver, // 向下位机发送数据开始标志
uart_rece_flag=0; // 接收上位机数据完成标志
unsigned char dir_flag= FORWARD; // 电机转动方向标志
unsigned char UartSendData[10]={0}, // 发送给下位机和PC的数据
Rec_data[10]={0}; // 从PC接收到的数据
signed int Num_Speed = 0; // 记录码盘返回的当前速度脉冲数
signed int temp_sint=0; // 控制电机用的PWM值,对应占空比
long TargetPosition; // 目标位移
long distance=MIDDISTANCE; // 已走位移
void port_init(void)
{
PORTB = 0x00; //0000 0110
DDRB = 0x00; //0000 0110
PORTC = 0x0C; //pc2,pc3--1,out
DDRC = 0x0C;
PORTD = 0x20;
DDRD = 0x00;
}
//TIMER0 initialize - prescale:1024
// desired value: 20mSec
void timer0_init(void)
// timer0 20ms中断初始化,采样周期T=20ms
{
TCCR0 = 0x00; //stop
TCNT0 = 0x29; //set count
TCCR0 = 0x05; //start timer
}
#pragma interrupt_handler timer0_ovf_isr:10
void timer0_ovf_isr(void)
// 名称:20ms溢出中断服务程序
// 实现功能:
// 1. 调用闭环控制函数
// 2. 向MCU1发送电机控制的PWM波占空比;同时,向上位机发送当前的速度值和位移值
{
TCNT0 = 0x29; //reload counter value
Num_Speed = TCNT1; //得到T1反馈回的20ms内的编码器脉冲数
TCNT1H = 0x00; //计数器1清0
TCNT1L = 0x00;
distance+=Num_Speed; // 更新已走位移
TargetPosition-=Num_Speed; // 更新剩余位移
// ************ 需要调整这里的移位数,因为尚不知道电机最大转速下,每个周期的脉冲数 *************//
// 测得电机在最大转速下,该脉冲数字在350~450之间
Num_Speed>>=1; // 速度脉冲数/2, 折算到0~255
sPID.vi_FeedBack = Num_Speed; // 储存当前速度值,速度最大就是255,再大就不可能实现
//************************ 在这里调用PID函数***********************//
temp_sint = v_PIDCalc(&sPID); // 计算得到控制的PWM值,在10到550之间,对应的是占空比
// 550 是由下位机中的ICR1设置的,对应PWM方式14的TOP值
if(TargetPosition<=DeadError) // 处理进入死区的情况
{
temp_sint=1;
}
//更新要发送的数据
if(dir_flag==BACKWARD)
{
UartSendData[1]=temp_sint>>8;
UartSendData[1]+=1<<7;//置方向位,为1为反转
UartSendData[2]=temp_sint;
}
else if(dir_flag==FORWARD)
{
UartSendData[1]=temp_sint>>8;
UartSendData[2]=temp_sint;
}
else
{
UartSendData[1]=UartSendData[2]=0;
}
UartSendData[3]=Num_Speed>>8;
UartSendData[4]=Num_Speed;
UartSendData[5]=distance>>16;
UartSendData[6]=distance>>8;
UartSendData[7]=distance;
//置发送标志位
VelSendFlag=SendStart;
}
//TIMER1 initialize - prescale:Rising edge
// WGM: 0) Normal, TOP=0xFFFF
// desired value: 1Hz
// actual value: Out of range
void timer1_init(void)
// timer1用来记录一个20ms周期内,从编码盘得到的脉冲数,采用计数方式
{
TCCR1B = 0x00; //stop
TCNT1H = 0x00 /*INVALID SETTING*/; //setup
TCNT1L = 0x00 /*INVALID SETTING*/;
TCCR1B = 0x07; //start Timer
}
//UART0 initialize
// desired baud rate: 9600
// actual: baud rate:9600 (0.0%)
// char size: 8 bit
// parity: Disabled
void uart0_init(void)
{
UCSRB = 0x00; //disable while setting baud rate
UCSRA = 0x00;
UCSRC = BIT(URSEL) | 0x06;
UBRRL = 0x47; //set baud rate lo
UBRRH = 0x00; //set baud rate hi
UCSRB = 0x98;
}
#pragma interrupt_handler uart0_rx_isr:12
void uart0_rx_isr(void)
// 从上位机接收数据的中断
{
//uart has received a character in UDR
static unsigned char data,rece_num=0,i=0;
data=UDR;
if((data==0xAA)&&(rece_num==0))//判断第1字节,0XAA起始位判断
{
TIMSK = 0x00; //timer interrupt sources
Rec_data[rece_num]=data;
rece_num++;
}
else if(rece_num>0)//第2字节到第8字节寄存到接收数据中
{
Rec_data[rece_num]=data;
rece_num++;
if(rece_num==8)//接收完毕,标志位置1
{
rece_num=0; //准备接收下一桢
uart_rece_flag=1;
}
}
}
#pragma interrupt_handler int0_isr:2
void int0_isr(void)
{
//external interupt on INT0
}
#pragma interrupt_handler int1_isr:3
void int1_isr(void)
{
//external interupt on INT1
}
//call this routine to initialize all peripherals
void init_devices(void)
{
//stop errant interrupts until set up
CLI(); //disable all interrupts
port_init();
timer0_init();
timer1_init();
uart0_init();
MCUCR = 0x0A;
GICR = 0xC0;
TIMSK = 0x05; //timer interrupt sources
SEI(); //re-enable interrupts
//all peripherals are now initialized
}
// *************** main ****************//
void main(void)
{
char i=0;
init_devices();
PIDInit(); // 调用PID参数初始化函数
TargetPosition=0x0000;
UartSendData[0]=0xAA; // 发送到下位机或PC的起始位
while(1)
{
//---------------start if( uart_rece_flag==1)
if( uart_rece_flag==1) // 接收完数据后进行数据处理
{
uart_rece_flag=0; // 清接收完成标志
PIDInit(); // 防止每次运行时出现超调现象,
// 因为上次运行结束后, sPID.vi_PreU 不为0,各变量均应该清0
dir_flag=Rec_data[1]; //电机方向
sPID.vi_Ref =Rec_data[2]; //给定速度大小
TargetPosition=Rec_data[3]; //目标位置
TargetPosition<<=12;
distance=MIDDISTANCE; //位移
sPID.v_Ka=Rec_data[4]; //参数Ka = Kp
sPID.v_Kb=Rec_data[5]; //参数Kb = T / Ti
sPID.v_Kb=Rec_data[6]; //参数Kc = ( Td / T )
TIMSK = 0x01; //timer interrupt sources
/*********************目标位置处理原理*****************************
设定值为丝杠转过的转数,则电机需要转过的转数为:设定转数×25
则编码器输出的脉冲为:设定转数×25×500=设定转数×12500
在程序中的处理为:假高设定转数为temp,则temp=<<10,即temp=temp×1024。
TargetPosition=temp<<2;//4倍
TargetPosition+=temp<<3;//8倍
这样处理之后,TargetPosition的值为:设定转数×12×1024=设定转数×12288
(12500-12288)/12500=0.01696=%1.696,误差非常小。
实际测试时设定的转数也能非常准确的到达,表明上述误差可忽略不计。
*******************************************************************/
}
//---------------end if( uart_rece_flag==1)
//---------------start if(VelSendFlag==SendStart)
if(VelSendFlag==SendStart) // 开始发送数据
{
VelSendFlag=SendOver; // 清开始发送标志
for(i=0;i<=LENGTH;i++)
{
while (!(UCSRA&(1<<UDRE))); // 等待发送寄存器为空
UDR=UartSendData[i]; // 为空时,发送下一字节
}
}
//---------------end if(VelSendFlag==SendStart)
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -