590 likes | 719 Views
实验三:直流电机的驱动 与编码器脉冲的采集. 内容概要. 学习 1 种 ATmega64 资源 —— 定时、计数器 3 演示 2 个实验: ( 1 )直流电机的驱动、调速 ( 2 )上述实验的软件仿真 自行做 3 个实验: ( 1 )直流电机的驱动 ( 2 )直流电机的 PWM 调速 ( 3 )光电编码器脉冲信号的读取. 控制什么?. 控制什么?. 用到哪些单片机资源?. 单片机模块 —— 电机控制. 通信模块 —— 和 PC 通信. 电机 驱动 模块 —— 驾驭电机. 电源模块 —— 提供动力.
E N D
实验三:直流电机的驱动 与编码器脉冲的采集
内容概要 • 学习1种ATmega64资源 ——定时、计数器3 • 演示2个实验: • (1)直流电机的驱动、调速 • (2)上述实验的软件仿真 • 自行做3个实验: • (1)直流电机的驱动 • (2)直流电机的PWM调速 • (3)光电编码器脉冲信号的读取
用到哪些单片机资源? 单片机模块—— 电机控制 通信模块——和PC通信 电机 驱动 模块——驾驭电机 电源模块——提供动力 输入输出模块——按键控制和状态指示
让直流电机动起来! • M+和M-分别接电源和地,电机就可以转动 • 交换M+和M-,电机就反向转动
自己动手之一——驱动直流电机 • 指令的输入:通过按键 • 按钮Stop、F.W.、B.W.作为控制按钮 • Stop(PC3,可触发外部中断INT5):启动、停止 • F.W.、B.W.( PC0、PC1,可触发外部中断4):正、反转
调试灯 • 调试用LED灯,可表示正、反转状态,运行状态等,也可以自定义。
电机控制I/O • PB4、PB5与L298的输入引脚相连 • 注意:小心滑块撞到支架!
操作说明 • 进行电机实验前,确保各接口连接好后,再打开电源。 • 在使用电机模块时,先打开电源开关,再打开L298使能开关;关闭时,先关闭L298使能开关,再关闭电源开关。 电源开关 L298使能开关
标志位的设定 • #define RUN 1 • #define STOP 0 • #define FORWARD 0 • #define BACKWARD 1 • unsigned char DC_State = STOP; // 电机状态,STOP或RUN • unsigned char dir_flag = FORWARD; // 方向标志,FORWARD或BACKWARD
宏定义 • #define isButtonFWDown() ((PINC&0B00000001)==0) • #define isButtonBWDown() ((PINC&0B00000010)==0) • #define isButtonZERODown() ((PINC&0B00000100)==0) • #define isButtonSTOPDown() ((PINC&0B00001000)==0) • #define LedFWOn() (PORTA &= 0B11111110) • #define LedBWOn() (PORTA &= 0B11111101) • #define LedZeroOn() (PORTA &= 0B11111011) • #define LedRunOn() (PORTA &= 0B11110111) • #define LedFWOff() (PORTA |= 0B00000001) • #define LedBWOff() (PORTA |= 0B00000010) • #define LedZeroOff() (PORTA |= 0B00000100) • #define LedRunOff() (PORTA |= 0B00001000)
自定义函数 • void DC_Forward() • { • PORTB |= 0B00010000; • PORTB &= 0B11011111; • } • void DC_Backward() • { • PORTB |= 0B00100000; • PORTB &= 0B11101111; • } • void DC_Stop() • { • PORTB &= 0B11001111; • } • 使用宏定义与自定义函数,可以提高程序的可读性与可维护性。 • 而宏定义与自定义函数相比,程序的执行效率更高。
外部中断4的处理程序举例 • #pragma interrupt_handler int4_isr:6 • void int4_isr() • { • //external interupt on INT4 • delay_ms(50); // 软件防抖 • if ( DC_State == RUN ) { • if ( isButtonFWDown() ) • { • dir_flag = FORWARD; • LedFWOn(); • LedBWOff(); • } • else if ( isButtonBWDown() ) • { • dir_flag = BACKWARD; • LedBWOn(); • LedFWOff(); • } • } • }
外部中断5的处理程序举例 • #pragma interrupt_handler int5_isr:7 • void int5_isr() • { • //external interupt on INT5 • delay_ms(50); • if ( isButtonSTOPDown() ) { • if ( DC_State == RUN ) • { • DC_State = STOP; • LedRunOff(); • DC_Stop(); • } • else if ( DC_State == STOP ) • { • DC_State = RUN; • LedRunOn(); • } • } • else if ( isButtonZERODown() ) • { • } • }
主函数 • while(1) • { • if (DC_State == RUN ) • { • if ( dir_flag == FORWARD ) • DC_Forward(); • else if ( dir_flag == BACKWARD ) • DC_Backward(); • } • }
如何改变电机的转速? • 对L298的控制有单极性、双极性2种方式。 • 单极性工作方式指的是在一个PWM周期内电机的电枢只承受单极性的电压; • 双极性工作方式是指在一个PWM周期内电机电枢两端的电压呈正负交替变化。
PWM波 H M+ L 转动时间 停止时间 电机正转 电机调速(单极性) • 以电机正转为例,在M+端产生PWM波,M-接低电平。则M+为高电平时,电机正转;为低电平时,电机停转。 • 改变PWM波的占空比即可改变电机的转速,占空比越大,电机转速越快。 • 脉冲宽度(简称脉宽),就是高电平的时间。 PWM(Pulse Width Modulation):脉冲宽度调制
如何产生PWM波?(T/C1) • 16位的T/C 可以实现精确的程序定时(事件管理)、波形产生和信号测量。其主要功能如下: • 用作定时器:可以用作频率发生器等; • 用作计数器:可用来对外部引脚的脉冲信号进行计数; • 输入捕获功能:可用来测定外部信号的脉宽; • 输出比较匹配功能:可用来产生脉宽调制信号(PWM波)。
相关引脚 • T1(PD6) • OC1A(PB5) • OC1B(PB6) • OC1C(PB7) • ICP1(PD4)
M+ 转动时间 停止时间 电机正转 电机调速(单极性,快速PWM模式) TOP OCR1A TCNT1 溢出时置位,并进行 电机转向控制 BOTTOM TOP(ICR1) OCR1A 比较匹配时清零
软件设计 • PWM波形产生方法: 我们采用大致20KHz以上的PWM波进行电机的调速,采用Timer1的模式14,快速PWM模式,分频为1。使能overflow interrupt 和compare A interrupt中断,选择OC1A output mode为disconnected普通模式,在匹配中断中清零PB4、PB5,溢出中断中根据方向标志置位PB4或PB5。 这样就用单极性方式产生了方向可变的PWM波形。
快速PWM模式 • TOP:用来定义通过波形发生器产生的波形的周期(频率)。我们要求在20kHz,采用快速PWM模式(模式14),TOP值由ICR1寄存器给定。 • OCR1A:输出比较寄存器,记录输出比较的脉冲数,即调节占空比。这样,如果可以随时改变OCR1A的值,就可以产生可调占空比的PWM波。在模式14中,OCR1A的更新在TCNT1达到TOP时刻。
输出比较中断 OCR1A =? TCNT1←TCNT1+1 比较匹配的标志位 OCF1A置位 (TIFR) 产生PWM 波形可从引脚 OC1A输出 总开关I打开? (SREG) 分开关OCIE1A 打开?(TIMSK) 输出比较 中断服务 清空 OCF1A
输出比较单元 • 双缓冲输出比较寄存器OCR1A一直与TCNT1 的值做比较。 • 波形发生器用比较结果产生PWM或在输出比较引脚OC1A输出可变频率的信号。 • 比较匹配结果还可置位比较匹配标志OCF1A,用来产生输出比较中断请求。
相关寄存器 • SREG(状态寄存器) • Bit 7 – I: 全局中断使能(总开关) • I 置位时使能全局中断。单独的中断使能由其他独立的控制寄存器控制。如果I 清零,则不论单独中断标志置位与否,都不会产生中断。
相关寄存器 • TIMSK(定时器中断屏蔽寄存器)(分开关) • Bit 5 – TICIE1: T/C1 输入捕捉中断使能 • Bit 4 – OCIE1A:T/C1 输出比较 A 匹配中断使能 • Bit 3 – OCIE1B:T/C1 输出比较 B 匹配中断使能 • Bit 2 – TOIE1:T/C1 溢出中断使能
相关寄存器 • TIFR(定时器中断标志寄存器) • Bit 5 – ICF1: T/C1 输入捕捉标志 • Bit 4 – OCF1A: T/C1 输出比较 A 匹配标志 当TCNT1 与OCR1A 匹配成功时,该位被设为"1”。 • Bit 3 – OCF1B: T/C1 输出比较 B 匹配标志 • Bit 2 – TOV1: T/C1 溢出标志
相关寄存器 • TCCR1A(T/C1控制寄存器A) • TCCR1B(T/C1控制寄存器B) • TCCR1C(T/C1控制寄存器C)
TCCR1C(T/C1控制寄存器C) • • Bit 7 – FOCnA: 强制输出比较通道A • • Bit 6 – FOCnB: 强制输出比较通道B • • Bit 5 – FOCnC: 强制输出比较通道C
相关寄存器(5个16位) • TCNT1(TCNT1H、TCNT1L) (16位的T/C1定时数据寄存器) • OCR1A(OCR1AH、OCR1AL) (16位的T/C1双缓冲输出比较寄存器A) • OCR1B(OCR1BH、OCR1BL) (16位的T/C1双缓冲输出比较寄存器B) • OCR1C(OCR1CH、OCR1CL) (16位的T/C1双缓冲输出比较寄存器C) • ICR1(ICR1H、ICR1L)(16位的T/C1输入捕获寄存器)
自己动手之二——改变电机转速 • Q:如何改变转速? • A:通过串口改变OCR1A的值,从而改变PWM占空比。 • 通过串口由PC机改变OCR1A的值,从而改变直流电机的转速!
单极性方式 • 输入端In1为PWM信号,输入端In2为低电平,电动机正转;输入端In2为PWM信号,输入端In1为低电平,电动机反转。 • 占空比为0%时制动,为100%时达到最高速; • 当EnA为低电平时,驱动桥路上的4个晶体管全部截止,使正在运行的电动机电枢电流反向,电动机自由停止。 • 但是,控制板的使能端通过按键开关接电源——用急停方式
ICCAVR中如何设置T/C1 采用普通方式。 电机采用单极性方式驱动
定时/计数器1的初始化举例 • void timer1_init(void) • { • TCCR1B = 0x00; //stop • TCNT1H = 0x00; //setup • TCNT1L = 0x00; • OCR1AH = 0x02; • OCR1AL = 0x27; • OCR1BH = 0x02; • OCR1BL = 0x27; • OCR1CH = 0x02; • OCR1CL = 0x27; • ICR1H = 0x02; // 20kHz • ICR1L = 0x27; • TCCR1A = 0x02; // OCR1A未连接,做普通端口操作 • TCCR1B = 0x19; // start Timer • } ( 0227 )H=( 551 )D
定时/计数器1的比较匹配中断 • #pragma interrupt_handler timer1_compa_isr:iv_TIM1_COMPA • void timer1_compa_isr(void) • { • //compare occured TCNT1=OCR1A • DC_Stop(); • }
定时/计数器1的溢出中断处理 • #pragma interrupt_handler timer1_ovf_isr:iv_TIM1_OVF • void timer1_ovf_isr(void) • { • //TIMER1 has overflowed • TCNT1H = 0x00; //reload counter high value • TCNT1L = 0x00; //reload counter low value • if (DC_State == RUN ) • { • if ( dir_flag == FORWARD ) • DC_Forward(); • else if ( dir_flag == BACKWARD ) • DC_Backward(); • } • else DC_Stop(); • }
通信协议 • 每帧包含3字节数据: • 字节0:0xaa (帧开始标志) • 字节1:速度高4位(bit3~bit0) • 字节2:速度低8位 • 速度范围(0x0001~0x0227),参见TIMER1初始化中ICR1取值
串口通信程序举例 • unsigned char ReceivedData[3]; // 接收缓存 • unsigned char receive_flag=0; // 接收完成标志,1表示接收一帧结束 • #pragma interrupt_handler uart1_rx_isr:iv_USART1_RXC • void uart1_rx_isr(void) • { • static unsigned char rece_num=0; • unsigned char data; • data=UDR1; • if(( 0xAA == data )&&( 0 == rece_num ) || rece_num > 0 ) • { ReceivedData[ rece_num ] = data; • rece_num++; } • if ( 3 == rece_num ) • { rece_num = 0; • receive_flag = 1; } • }
主函数中修改脉冲宽度 • while(1) • { • if (receive_flag == 1) • { • OCR1AH = ReceivedData[ 1 ]; • OCR1AL = ReceivedData[ 2 ]; • DC_State = RUN; • receive_flag = 0; • } • }