实验六 直流电机脉宽调制调速 pwm应用实例 C51实现 附累加进位法正确性证明

实验内容

  1. 在液晶显示屏上显示出直流电机的:当前转速、低目标转速、高目标转速。
  2. 固定向P1.1输出0,然后测量每秒钟电机转动的转数,将其显示在数码管,每秒刷新一次即可。
  3. 使用脉宽调制的方法,动态调整向P1.1输出的内容,使得电机转速能够稳定在一个预定值附近,同时实时显示当前转速。
  4. 根据输入修改电机得目标转速值,设置两个转速目标值:低转速和高转速。
  5. 每隔一秒钟读取两个开关的状态,如果S1按下,动态调整输出,使得电机转速能够稳定到低转速目标值附近,如果S2按下,动态调整输出,使得电机转速能够稳定到高转速目标值附近。交替显示目标值和当前转速值。

有不懂或不对的地方->评论区留言

1 基础知识

1.1 占空比

使用单片机实现PWM,就是根据预定的占空比δ来输出0和1,这里δ就是控制变量。最简单的办法就是以某个时间单位(如0.1ms,相当于10kHz)为基准,在前N段输出1,后M-N段输出0,总体的占空比就是N/M。这种方法由于0和1分布不均匀,所以要求基准频率要足够高,否则会出现颠簸现象

1.2 累加进位法:

设置一个累加变量x,每次加N,若结果大于等于M,则输出1,并减去M;否则输出0。这样整体的占空比也是N/M。在实验中取M=256可以使程序更加简单。
另外,由于本实验板的设计,输出0使电机工作。因此对于本实验,上面所说的0和1要翻转过来用。
(注意: 结果大于等于M,不是大于M,具体原因看思考题第二问)

1.3 反馈控制

1.3.1 涉及到的变量

进行转速控制时,涉及到三个变量:预期转速,实际转速和控制变量。这里控制变量就是占空比。我们并不能够预先精确知道某个控制变量的值会导致多少的实际转速,因为这里有很多内部和外部因素起作用(如摩擦力,惯性等),但可以确定就是随着控制变量的增加,实际转速会增加

1.3.1 反馈控制基本原理

反馈控制的基本原理就是根据实际结果与预期结果之间的差值,来调节控制变量的值。当实际转速高于预期转速时,我们需要减少控制变量,以降低速度;反之则需要调高控制变量。

这次我把思考题放到了前面,好不容易画一回图,当然得放前面啦( •̀ ω •́ )✧

2 思考题

  1. 讨论脉宽调速和电压调速的区别、优缺点和应用范围。
  • 区别:
    脉宽调制(Pulse Width Modulation,PWM)是一种能够通过开关量输出达到模拟量输出效果的方法。脉宽调速就是利用脉宽调制,以数字方式调节电机转速。
    而电压调速,则是通过:直接调节电压这个模拟量来调节电机转速。
  • 优缺点(脉宽调速对应于PWM,电压调速对应于模拟电路):

尽管模拟控制看起来可能直观而简单,但它并不总是非常经济或可行的。其中一点就是,模拟电路容易随时间漂移,因而难以调节。能够解决这个问题的精密模拟电路可能非常庞大、笨重(如老式的家庭立体声设备)和昂贵。模拟电路还有可能严重发热,其功耗相对于工作元件两端电压与电流的乘积成正比。模拟电路还可能对噪声很敏感,任何扰动或噪声都肯定会改变电流值的大小。

  • 通过以数字方式控制模拟电路,可以大幅度降低系统的成本和功耗。此外,许多微控制器和DSP已经在芯片上包含了PWM控制器,这使数字控制的实现变得更加容易了。
  • PWM的一个优点是从处理器到被控系统信号都是数字形式的,无需进行数模转换。让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响。(来源

应用范围:

电压调速工作时不能超过特定电压,优点是机械特性较硬并且电压降低后硬度不变,稳定性好,适用于对稳定性要求较高的环境。脉宽调速可大大节省电量,具有很强的抗噪性,且节 约空间、比较经济,适用于低频大功率控制。(这段是从上届学长学姐那摘的,因为我没找到相关的资料。。。)

  1. 说明程序原理中累加进位法的正确性。
    先来举几个例子感受和验证一下
    在这里插入图片描述

占空比:128/256=1/2


在这里插入图片描述

占空比:64/256=1/4


在这里插入图片描述

占空比:192/256=3/4


在这里插入图片描述

占空比:129/256
注意第二排的“128 1”那块。从x=129到第二次x=129之间有256个时间片,其中有129个是高电平,127个是低电平,多出来的那个高电平就是“128 1”里的那个x=1时的高电平。
证明:
x=x+N x<M的为低,x从N开始,到刚好x>=M之前有(M/N-1)次低电平,之后会有一次高电平。
这是从平均的角度来说的,即:平均(M/N-1)次低电平后会有一次高电平
因为M/N不一定能恰好除尽,所以我们两边同时乘上N
得 :(M-N)次低电平 N次高电平。恰好对应于占空比的概念。

  1. 计算转速测量的最大可能误差,讨论减少误差的办法。
    最多差1转。
    如果转轮的那个孔起始位置在光电对管旁边且刚好挡住光电对管,也就是只要稍微转一转就会触发一次外部中断,那测第一转的时候就会多算一转。1s时间到的时候也可能恰好挡住光电对管,那就会少算一转。
    减少误差的办法:
    如果在变速过程中我觉得这种误差是无法避免的,如果是最后稳定状态了,那相邻两次测量结果取平均就好了,因为你上次多算一转,下次就会少算一转,一取平均刚刚好。

3 设置INT0为下降沿触发

IT0=1;

3.1 控制寄存器 TCON

控制寄存器 TCON 的高 4 位存放定时器的运行控制位和溢出标志位,低 4 位 存放外部中断控制位,格式如下:
在这里插入图片描述
ITn 是外部中断的触发方式控制位。当 ITn=0 时是电平触发方式,外部引脚 出现低电平时产生中断请求。当 ITn=1 时是边沿触发方式。

4 需要你去思考的地方

调整占空比其实就是调整N,具体的对N的调整策略会影响到对转速控制效果的好坏。
N变化太小,则速度的调整会过慢。N变化太大,则速度的抖动又会太大,不容易达到稳态。
所以你需要去思考如何更好地调整N来达到更好的控制效果。
本文的代码中给出了我自己的调整策略,那块也有详尽的注释。如果你有更好的调整策略,还请不吝赐教。

5 完整代码

注意:本代码的显示没有完全按照实验内容要求的“交替显示目标值和当前转速值”而是同时显示两者了,因为我觉着这样比较清楚。

#include <REG51.h>
#include <intrins.h>
int cur_speed = 0, low_target = 100, high_target = 180;
int speed = 0;
int x = 0;
int M = 512;
int CUR_N = 96, LOW_N = 72, HIGH_N = 128; //这是M=512时对应的参数
//之所以选择512而不是256主要是因为这样可以使接近目标速度时更容易进行微小的调整

//int CUR_N=48,LOW_N=36,HIGH_N=64;//这是M=256时对应的参数
//N 为48时 转速为130-155zhuan/s
//64 190z/s 这个转速只是个大概,不同的机器,相同的机器不同的时间都会有差别
//36 94z/s

//LCD显示部分对实验5的LCD显示部分稍作修改就行
enum SCREEN_CHOICE
{
    
    
    LEFT,  //左半屏
    RIGHT, //右半屏
    WHOLE  //全屏
};
char DISPALY_PAGE = 0;
//定义LCD引脚
#define LCD_DATA P2 //P2口为LCD的数据口
sbit RST = P1 ^ 5;  //复位引脚
sbit CS1 = P1 ^ 7;  //左半屏选择
sbit CS2 = P1 ^ 6;  //右半屏选择
sbit BUSY = P2 ^ 7; //BUSY位
sbit E = P3 ^ 3;    //使能引脚
sbit RW = P3 ^ 4;   //读/写选择器引脚(R/W)
sbit RS = P3 ^ 5;   //数据/命令选择器引脚(R/S)
//LCD 汉字字码 一个汉字32个字节,分别为“当”,“前”,“转”,“速”,“低”,“目”,“标”,“高”
char code dang[] = {
    
    0x00, 0x40, 0x42, 0x44, 0x58, 0x40, 0x40, 0x7F, 0x40, 0x40, 0x50, 0x48, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x40, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0xFF, 0x00, 0x00, 0x00};
char code qian[] = {
    
    0x08, 0x08, 0xE8, 0x29, 0x2E, 0x28, 0xE8, 0x08, 0x08, 0xC8, 0x0C, 0x0B, 0xE8, 0x08, 0x08, 0x00, 0x00, 0x00, 0xFF, 0x09, 0x49, 0x89, 0x7F, 0x00, 0x00, 0x0F, 0x40, 0x80, 0x7F, 0x00, 0x00, 0x00};
char code zhuan[] = {
    
    0xC8, 0xB8, 0x8F, 0xE8, 0x88, 0x88, 0x40, 0x48, 0x48, 0xE8, 0x5F, 0x48, 0x48, 0x48, 0x40, 0x00, 0x08, 0x18, 0x08, 0xFF, 0x04, 0x04, 0x00, 0x02, 0x0B, 0x12, 0x22, 0xD2, 0x0A, 0x06, 0x00, 0x00};
char code su[] = {
    
    0x40, 0x40, 0x42, 0xCC, 0x00, 0x04, 0xF4, 0x94, 0x94, 0xFF, 0x94, 0x94, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x40, 0x20, 0x1F, 0x20, 0x48, 0x44, 0x42, 0x41, 0x5F, 0x41, 0x42, 0x44, 0x48, 0x40, 0x00};
char code di[] = {
    
    0x00, 0x80, 0x60, 0xF8, 0x07, 0x00, 0xFC, 0x84, 0x84, 0x84, 0xFE, 0x82, 0x83, 0x82, 0x80, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x40, 0x20, 0x00, 0x41, 0x8E, 0x30, 0x40, 0xF8, 0x00};
char code mu[] = {
    
    0x00, 0x00, 0xFE, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0xFF, 0x00, 0x00, 0x00};
char code biao[] = {
    
    0x10, 0x10, 0xD0, 0xFF, 0x90, 0x10, 0x20, 0x22, 0x22, 0x22, 0xE2, 0x22, 0x22, 0x22, 0x20, 0x00, 0x04, 0x03, 0x00, 0xFF, 0x00, 0x13, 0x0C, 0x03, 0x40, 0x80, 0x7F, 0x00, 0x01, 0x06, 0x18, 0x00};
char code gao[] = {
    
    0x04, 0x04, 0x04, 0x04, 0xF4, 0x94, 0x95, 0x96, 0x94, 0x94, 0xF4, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0xFE, 0x02, 0x02, 0x7A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x7A, 0x02, 0x82, 0xFE, 0x00, 0x00};
//LCD 数字字码 一个数字16个字节
char code NUMBER[10][16] = {
    
    {
    
    0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x10, 0x0F, 0x00}, /*"0"*/
                            {
    
    0x00, 0x10, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00}, /*"1"*/
                            {
    
    0x00, 0x70, 0x08, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x30, 0x28, 0x24, 0x22, 0x21, 0x30, 0x00}, /*"2"*/
                            {
    
    0x00, 0x30, 0x08, 0x88, 0x88, 0x48, 0x30, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00}, /*"3"*/
                            {
    
    0x00, 0x00, 0xC0, 0x20, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x07, 0x04, 0x24, 0x24, 0x3F, 0x24, 0x00}, /*"4"*/
                            {
    
    0x00, 0xF8, 0x08, 0x88, 0x88, 0x08, 0x08, 0x00, 0x00, 0x19, 0x21, 0x20, 0x20, 0x11, 0x0E, 0x00}, /*"5"*/
                            {
    
    0x00, 0xE0, 0x10, 0x88, 0x88, 0x18, 0x00, 0x00, 0x00, 0x0F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00}, /*"6"*/
                            {
    
    0x00, 0x38, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, /*"7"*/
                            {
    
    0x00, 0x70, 0x88, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x1C, 0x22, 0x21, 0x21, 0x22, 0x1C, 0x00}, /*"8"*/
                            {
    
    0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x31, 0x22, 0x22, 0x11, 0x0F, 0x00},
                            /*"9"*/};
//英文字符,/*"/" 和 "s",":"*/
char code per[] = {
    
    0x00, 0x00, 0x00, 0x00, 0xC0, 0x38, 0x04, 0x00, 0x00, 0x60, 0x18, 0x07, 0x00, 0x00, 0x00, 0x00};
char code s[] = {
    
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x33, 0x24, 0x24, 0x24, 0x24, 0x19, 0x00};
//colon是冒号的意思
char code colon[] = {
    
    0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00};
//函数声明
void delay_nops(int);
void turn_on_display();
void display_result();
void init_lcd();
void wait_until_not_busy();
void choose_screen(enum SCREEN_CHOICE screen);
void clear(enum SCREEN_CHOICE);
void set_page(char);
void set_col(char);

//LCD初始化函数
void init_lcd()
{
    
    
    wait_until_not_busy();
    RST = 0; //复位
    delay_nops(200);
    RST = 1;
    delay_nops(200);
    turn_on_display();
    clear(WHOLE); //清屏 这个必须有!因为复位后显示RAM不会自动清0
}

//读取LCD状态字
void get_lcd_sw()
{
    
    
    RS = 0;
    RW = 1;
    E = 1;
}
//等待直到LCD非忙
void wait_until_not_busy()
{
    
    
    get_lcd_sw();
    while (BUSY)
    {
    
    
    }
    // E = 0;
}
//写控制lcd代码
void set_lcd(char control_code)
{
    
    
    wait_until_not_busy();
    RS = 0;
    RW = 0;
    LCD_DATA = control_code;
    //下跳沿写入
    E = 1;
    delay_nops(200);
    E = 0;
}
//写LCD数据
void lcd_data_write(char lcd_data)
{
    
    
    wait_until_not_busy();
    RS = 1;
    RW = 0;
    LCD_DATA = lcd_data;
    //下跳沿写入
    E = 1;
    delay_nops(200);
    E = 0;
}
//设置显示行
void set_row(char row)
{
    
    
    row = row & 0x3f; //Row范围0到63,高两位清零
    set_lcd(0xc0 + row);
}
//设置显示列
void set_col(char col)
{
    
    
    col = col & 0x3f; //Col范围0到63,高两位清零
    set_lcd(0x40 + col);
}
//设置显示页
void set_page(char page)
{
    
    
    page = page & 0x07; //Page范围0到7,取低三位
    set_lcd(0xb8 + page);
}
//开显示
void turn_on_display()
{
    
    
    set_lcd(0x3f);
}
//选择屏
void choose_screen(enum SCREEN_CHOICE screen)
{
    
    
    switch (screen)
    {
    
    

    case LEFT:
        CS1 = 1;
        CS2 = 0;
        break; //左屏
    case RIGHT:
        CS1 = 0;
        CS2 = 1;
        break; //右屏
    case WHOLE:
        CS1 = 1;
        CS2 = 1;
        break; //全屏
    default:
        CS1 = 1;
        CS2 = 1;
        break; //全屏
    }
}
//清屏
void clear(enum SCREEN_CHOICE screen)
{
    
    
    int i, j;
    choose_screen(screen);
    for (i = 0; i < 8; i++)
    {
    
    
        set_page(i);
        set_col(0x00);
        /*注意这里的j是从0到63
			因为如果我们选择全屏的话,它也不是把整块屏幕算做一个屏,
			而是同时对左右两个半屏做相同的操作。
			所以最多能操作的列数还是64列。
			*/
        for (j = 0; j < 64; j++)
        {
    
    
            lcd_data_write(0x00);
        }
    }
}

//显示汉字
void display_Chinese(enum SCREEN_CHOICE screen, char page, char col, char *Chinese)
{
    
    
    //汉字是16*16,即16行,16列。一页有8行,所以需要两页
    int i = 0;
    choose_screen(screen);
    set_page(page);
    set_col(col);
    //汉字的上半部分
    for (i = 0; i < 16; i++)
    {
    
    
        lcd_data_write(Chinese[i]);
    }
    //汉字的下半部分
    set_page(page + 1);
    set_col(col);
    for (i = 16; i < 32; i++)
    {
    
    
        lcd_data_write(Chinese[i]);
    }
}
//显示英文字符
void display_number(enum SCREEN_CHOICE screen, char page, char col, char *English)
{
    
    
    //数字是16*8,即16行,8列。一页有8行,所以需要两页
    int i = 0;
    choose_screen(screen);
    set_page(page);
    set_col(col);
    //数字的上半部分
    for (i = 0; i < 8; i++)
    {
    
    
        lcd_data_write(English[i]);
    }
    //数字的下半部分
    set_page(page + 1);
    set_col(col);
    for (i = 8; i < 16; i++)
    {
    
    
        lcd_data_write(English[i]);
    }
}
//这个函数用来清 低/高目标转速那行字
void clear_lower_part()
{
    
    
    int i, j;
    for (i = DISPALY_PAGE + 3; i <= DISPALY_PAGE + 4; i++)
    {
    
    
        choose_screen(WHOLE);
        set_page(i);
        set_col(0x00);
        for (j = 0; j < 64; j++)
        {
    
    
            lcd_data_write(0x00);
        }
    }
}
//显示当前转速
void display_result_cur()
{
    
    
    int cur_ones, cur_tens, cur_hundreds;

    cur_hundreds = cur_speed / 100;  //百位
    cur_tens = cur_speed % 100 / 10; //十位
    cur_ones = cur_speed % 10;       //个位

    display_Chinese(LEFT, DISPALY_PAGE, 0, dang);
    display_Chinese(LEFT, DISPALY_PAGE, 1 * 16, qian);
    display_Chinese(LEFT, DISPALY_PAGE, 2 * 16, zhuan);
    display_Chinese(LEFT, DISPALY_PAGE, 3 * 16, su);
    display_number(RIGHT, DISPALY_PAGE, 0, colon);
    display_number(RIGHT, DISPALY_PAGE, 1 * 8, NUMBER[cur_hundreds]);
    display_number(RIGHT, DISPALY_PAGE, 2 * 8, NUMBER[cur_tens]);
    display_number(RIGHT, DISPALY_PAGE, 3 * 8, NUMBER[cur_ones]);
    display_number(RIGHT, DISPALY_PAGE, 4 * 8, per);
    display_number(RIGHT, DISPALY_PAGE, 5 * 8, s);
}

//显示低目标转速
void display_result_low()
{
    
    

    int low_ones, low_tens, low_hundreds;
    display_result_cur();
    low_hundreds = low_target / 100;
    low_tens = low_target % 100 / 10;
    low_ones = low_target % 10;

    display_Chinese(LEFT, DISPALY_PAGE + 3, 0, di);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 1 * 16, mu);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 2 * 16, biao);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 3 * 16, zhuan);

    display_Chinese(RIGHT, DISPALY_PAGE + 3, 0, su);
    display_number(RIGHT, DISPALY_PAGE + 3, 16, colon);
    display_number(RIGHT, DISPALY_PAGE + 3, 3 * 8, NUMBER[low_hundreds]);
    display_number(RIGHT, DISPALY_PAGE + 3, 4 * 8, NUMBER[low_tens]);
    display_number(RIGHT, DISPALY_PAGE + 3, 5 * 8, NUMBER[low_ones]);
    display_number(RIGHT, DISPALY_PAGE + 3, 6 * 8, per);
    display_number(RIGHT, DISPALY_PAGE + 3, 7 * 8, s);
}
//显示高目标转速
void display_result_high()
{
    
    

    int high_ones, high_tens, high_hundreds;

    high_hundreds = high_target / 100;
    high_tens = high_target % 100 / 10;
    high_ones = high_target % 10;
    display_result_cur();
    //高目标转速
    display_Chinese(LEFT, DISPALY_PAGE + 3, 0, gao);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 1 * 16, mu);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 2 * 16, biao);
    display_Chinese(LEFT, DISPALY_PAGE + 3, 3 * 16, zhuan);

    display_Chinese(RIGHT, DISPALY_PAGE + 3, 0, su);
    display_number(RIGHT, DISPALY_PAGE + 3, 16, colon);
    display_number(RIGHT, DISPALY_PAGE + 3, 3 * 8, NUMBER[high_hundreds]);
    display_number(RIGHT, DISPALY_PAGE + 3, 4 * 8, NUMBER[high_tens]);
    display_number(RIGHT, DISPALY_PAGE + 3, 5 * 8, NUMBER[high_ones]);
    display_number(RIGHT, DISPALY_PAGE + 3, 6 * 8, per);
    display_number(RIGHT, DISPALY_PAGE + 3, 7 * 8, s);
}
/* 对于延时很短的,要求在us级的,采用"_nop_"函数,这个函数相当汇编NOP指令,延时几微秒。
   NOP指令为单周期指令,可由晶振频率算出延时时间,对于12M晶振,12/12M=1uS。*/
void delay_nops(int n)
{
    
    
    while (n--)
    {
    
    
        _nop_();
    }
}

sbit PWM_OUT = P1 ^ 1;
//开关
sbit S1 = P3 ^ 6;
sbit S2 = P3 ^ 7;

void int0_service() interrupt 0
{
    
    

    speed++;

    /*
	在本实验板中,电机每转动一次,与之相连的偏心轮将遮挡光电对管一次,
	因此会产生一个脉冲,送到INT0。
	要测量转速可以测量一秒种之内发生的中断次数。
	*/
}
//multiple:倍数。24个1/24s的定时器为1s
int multiple = 24;

void T0_service() interrupt 1
{
    
    
    int error;
    //TH0,TL0都是内部自带的寄存器,所以我们无需自己定义寄存器啥的,直接用就行
    TH0 = 0x5D;
    TL0 = 0x3E;

    /*因为执行到这时已经过去了一个定时时间,
	所以是--multiple不是multiple--
	即先减multiple后用multiple*/
    while (--multiple == 0)
    {
    
    
        cur_speed = speed;
        multiple = 24;
        if (S1 == 0) //s1按下 低目标转速
        {
    
    
            CUR_N = LOW_N;
            /*与low_target+1比较而不是与low_target比较
			是因为本程序未对速度进行取平均处理,
			所以最后稳定时速度有+-1的浮动
			*/
            if (cur_speed > (low_target + 1))
            {
    
    
                error = 0.1 * (cur_speed - low_target); //用这个来修正N
                /*如果error算出来特别小,那强制类型转换后error就是0了
				所以为了在这种情况下依旧可以调整N,我们可以令error为1
				*/
                if (error < 1)
                    error = 1;
                LOW_N -= error;
            }
            else if (cur_speed < (low_target - 1))
            {
    
    
                error = 0.1 * (low_target - cur_speed);
                if (error < 1)
                    error = 1;
                LOW_N += error;
            }
        }
        else if (S2 == 0) //s2按下 高目标转速
        {
    
    

            CUR_N = HIGH_N;
            if (speed > high_target + 1)
            {
    
    
                /*注意这里的系数是0.15而低目标时的系数是0.1
				系数是自己设的,不是用确定的公式计算得到的。
				之所以这里写0.15是因为
				如果是0.1那么当speed与high_target
				相差小于10的时候error就会被置为1,
				那就会导致speed就会每次变化1。
				也就是说大约10s后才能到目标值。
				当系数为0.2时,这个过程需要5s,
				但是系数越大,稳定前每次调整的幅度就越大,
				因为这个调整有一定的滞后性,
				所以如果调整的幅度比较大,就更不容易稳定。
				所以这里折中考虑选择了0.15;
				而慢速时,对调整幅度更加敏感,所以低目标那写的是0.1
				*/
                error = 0.15 * (speed - high_target);
                if (error < 1)
                    error = 1;
                HIGH_N -= error;
            }
            else if (speed < high_target - 1)
            {
    
    
                error = 0.15 * (high_target - speed);
                if (error < 1)
                    error = 1;
                HIGH_N += error;
            }
        }

        speed = 0;
    }
}

void T1_service() interrupt 3
{
    
    
    TH1 = 0XFF; //0.1ms
    TL1 = 0x9C;

    x += CUR_N;
    if (x >= M) //注意这里是大于等于M,不是大于M
    {
    
    
        PWM_OUT = 0; //输出0使电机工作
        x -= M;
    }
    else
    {
    
    
        PWM_OUT = 1;
    }
}

void init()
{
    
    

    TMOD = 0x11; //T0,T1作为定时器并工作在方式一

    TH0 = 0x5D; //1/24s
    TL0 = 0x3E;

    TH1 = 0XFF; //0.1ms
    TL1 = 0x9C;

    ET0 = 1; //允许定时器0 中断
    ET1 = 1; //允许定时器1 中断
    IT0 = 1; //设置INT0为下降沿触发
    EX0 = 1; //允许INT0 中断
    EA = 1;  //允许CPU响应中断
    TR0 = 1; //定时器0开始运行
    TR1 = 1; //定时器0开始运行
}
void main()
{
    
    
    init_lcd();
    init();

    while (1)
    {
    
    
        if (S1 == 0) //s1按下 低目标转速
        {
    
    
            display_result_low();
        }
        else if (S2 == 0) //s2按下 高目标转速
        {
    
    
            display_result_high();
        }
        else
        {
    
    
            display_result_cur();
            clear_lower_part(); //这里记得清一下 下面那行字,以表示此时没有键按下
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42378324/article/details/108775057