文章目录
做了一个实战项目,这个实战项目主要是实现对直流电机转速的控制,可以实现电机加速,减速,报警、启停以及显示转速。在本电路的基础上也可以进行一些拓展改变电机正反转的状态,只需要外加一个按键和修改部分程序即可,在文章的最后会对拓展进行一个说明。基础代码来源于普中科技的基础例程,代码经过我的验证并且实践到了实际项目中,如果想要进行实战测试的话,请仔细对照着我这篇文章进行调试学习,实际电路要对应好。
首先先分析需求:
51单片机有很多种,鉴于本人只有AT系列单片机的下载器,因此选用的芯片型号为AT89S52/AT89C52,都兼容,任意选择一种都可以。
直流电机驱动电路。这个比较通用的就几个,常见的有L298N驱动电路,由于是直接设计到电路板上,所以在网上copy了开源的直流电机驱动电路,就不用L298N驱动芯片了。直接通过三极管和二极管配合对马达进行驱动控制。
需要显示转速,用LCD1602进行显示,两行可以显示N多数据
需要进行测速,通用的就是霍尔传感器+磁铁,那么根据个人的需求,你可以选择低电平触发也可以选择高电平触发的霍尔传感器。其出发原理就是磁铁的一极靠近霍尔传感器的时候,霍尔传感器一引脚会产生触发电平,通过检查触发电平即可知道磁铁经过的次数。通常的软件检测方法是利用外部中断检测上升沿或者下降沿。
按键模块,普通的按键按下检测就可以
特此提醒一下,我用的霍尔传感器是低电平有效,因此在我的程序里面外部中断被设置为下降沿触发。只要是低电平有效的霍尔传感器都可以,只需要注意触发速率,因为电机转速很快,所以需要高速率的检测。
话不多说,直接上图。整体流程就是先将该初始化的初始化,比如外部中断,定时器一和定时器二中断。霍尔传感器的存在主要是为了检测转速,由于直流电机每转动一圈霍尔传感器就会产生一个下降沿,故可以通过判断下降沿的多少从而转换成某段时间内电机的转速。定时器一的功能是进行2s的计时,因为要统计一段时间内的下降沿个数,故通过定时器一进行赋值和复位。定时器二的作用是与设置的占空比进行比较,比如定义的占空比最大100%的时候对应一个参数量为200,那么可以设置一个定量duty为100,可以调节duty的大小,100的时候就代表50%的占空比,当计数小于100时输出为高电平,大于100时输出为低电平,这样就实现了PWM的输出。

下图是硬件连接图
## 2.硬件电路解读
除单片机最小系统以外,需要注意的地方有:
霍尔传感器的连接方式:在VCC和数据输出口也就是定义的P33引脚口需要接一个上拉电阻,在没有触发信号的时候,数据口能够一直以高电平的状态存在,这样不会存在误触发的情况。
直流电机驱动电路:尤其要注意三极管的型号和所处的位置,不要焊接错误了,焊接对了就能正常运行。想要让电机正常运行的话,只需要P34和P37之间存在着电平差,即P34高电平,P37低电平就可以运行,反之即可。
LCD背光显示:此处采用电阻直接分压,并没有用到滑动变阻器分压,背光固定,可通过改变电阻自由选择。
在参数定义部分,主要是定义多个数组。数组的作用是为了将数据存放并显示,由于LCD1602写入数据是通过地址写入的,在这里博主采用的是单字符逐步写入,所以将数据拆分开放到定义的数组里面,这样就能够单独进行显示。
unsigned char Limit[5]; //报警阈值存放位置
unsigned int max=10000; //设置报警阈值
unsigned char duty1[3]; //报警阈值存放位置
unsigned char zhuansu[5]; //转速存放位置
unsigned int zhuansu1=0; //显示转速大小
unsigned int zhuansu2=0; //显示转速大小
unsigned char code wenzi1[14]={"now_V: m/h"};
unsigned char code wenzi2[16]={"Limit: m "};
uint flag=0;
u16 duty=100; //占空比
u16 duty2=0;
u16 work_status=0; //工作状态,为0的时候不工作,为1的时候工作
IO口的定义分别为。P1.0-P1.4是按键控制口,分别代表电机加速,减速,报警阈值增加,报警阈值减少以及电机的启停。P1.5-P1.6分别代表LED灯控制端口和蜂鸣器控制端口,控制方式也就是给高低电平就能控制蜂鸣器鸣叫或者LED亮。P3.4和P3.7是控制直流电机两端的IO口,通过控制其中某个口为高电平,某个口为低电平就能实现直流电机的正转或者反转。此外,虽然没有定义,但是在外部中断里面对应的IO口为P3.3口,所以要通过杜邦线去连接霍尔传感器的数据输出端。
sbit beep=P1^6;
sbit led=P1^5;
sbit out=P3^4; //PWM输出用于正传
sbit out1=P3^7; //PWM输出用于反转
sbit duty_add=P1^0; //占空比加1
sbit duty_reduce=P1^1; //占空比减一
sbit limit_add=P1^2; //阈值加1
sbit limit_reduce=P1^3; //阈值减1
sbit start=P1^4; //开始或者停止工作
延时函数和按键处理函数。在按键处理函数里面,对五个按键进行扫描判断,当检测到按键按下以后执行相对应的程序。其中duty是我们定义的占空比数值,占空比为100%的时候duty为200,初始值duty设置为100,对应占空比为50%。通过判断第一个或者第二个按键按下从而实现对占空比的加减。同理第三个第四个也是对阈值进行判断,第五个按键有所不同的地方在于里面有work_status的切换,当然直接用!语句也行,我这里是用习惯了这种方式。在每个按键处理函数后面加一个while循环是为了防止连续按下导致疯狂加减。
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
while(i--);
}
/*******************************************************************************
* 函 数 名 : keypros
* 函数功能 : 按键处理函数,判断按键K1是否按下
*******************************************************************************/
void keypros()
{
if(duty_add==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(duty_add==0) //再次判断按键是否按下
{
duty+=1;
}
while(!duty_add); //检测按键是否松开
}
if(duty_reduce==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(duty_reduce==0) //再次判断按键是否按下
{
duty-=1;
}
while(!duty_reduce); //检测按键是否松开
}
if(limit_add==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(limit_add==0) //再次判断按键是否按下
{
max+=100;
}
while(!limit_add); //检测按键是否松开
}
if(limit_reduce==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(limit_reduce==0) //再次判断按键是否按下
{
max-=100;
}
while(!limit_reduce); //检测按键是否松开
}
if(start==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(start==0) //再次判断按键是否按下
{
work_status+=1;
if(work_status>=2)work_status=0;
}
while(!start); //检测按键是否松开
}
}
三个函数的初始化。在初始化函数里面对每一个都进行了配置,每条语句有其对应的解释,具体操作是通过寄存器层面去操作的,在reg52.h里面类似于IT1这种符号都有被定义。需要注意的是,定时器的工作模式为方式1。由于每一次单片机一个周期是1us,所以若想实现10ms的定时,就需要计10000个周期,那么定时器的TH和TL就可设置为65535-计数周期+1。其中TH代表高八位,TL代表低八位。比如定时10ms就为55536,用十六进制表示为D8F0。
/*******************************************************************************
* 函数名 : Int1Init()
* 函数功能 : 霍尔传感器接收信号
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Int1Init()
{
IT1=1;//下降沿触发
EX1=1;//打开中断0允许
EA=1; //打开总中断
}
/*******************************************************************************
* 函 数 名 : Timer0Init
* 函数功能 : 定时器0初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0Init()
{
TMOD|=0X11;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0XD8; //给定时器赋初值,定时10ms
TL0=0XF0;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
/*******************************************************************************
* 函 数 名 : Timer1Init
* 函数功能 : 定时器1初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1Init()
{
TMOD|=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
TH1=0XFF; //给定时器赋初值,定时100us
TL1=0X9C;
ET1=1;//打开定时器1中断允许
EA=1;//打开总中断
TR1=1;//打开定时器
}
主函数其实就是对所有的函数进行初始化以后,再对数据进行显示,所有控制方式生效都是在后续的定时器中断或者外部中断里面去执行。
*******************************************************************************
* 函数名 : main
* 函数功能 : 主函数
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void main()
{
unsigned char i;
beep=1; //蜂鸣器不叫
led=0;
Int1Init(); //初始化霍尔传感器外部中断1
Timer0Init(); //定时器0初始化
Timer1Init(); //定时器1初始化
LcdInit();
LcdWriteCom(0x80);
for(i=0;i<14;i++)
{
LcdWriteData(wenzi1[i]);
}
LcdWriteCom(0x80+0x40);
for(i=0;i<16;i++)
{
LcdWriteData(wenzi2[i]);
}
while(1)
{
duty2=duty/2;
if(zhuansu1>=max)beep=0,led=1;
else beep=1,led=0;
//按键处理
keypros();
//显示转速
zhuansu[0]=zhuansu1/10000;
zhuansu[1]=zhuansu1%10000/1000;
zhuansu[2]=zhuansu1%10000%1000/100;
zhuansu[3]=zhuansu1%10000%1000%100/10;
zhuansu[4]=zhuansu1%10;
if(zhuansu[0]>9)
{
LcdWriteCom(0x80+0x06); //设置显示位置
LcdWriteData(0x37+zhuansu[0]); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x06);
LcdWriteData(zhuansu[0]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[1]>9)
{
LcdWriteCom(0x80+0x07);
LcdWriteData(zhuansu[1]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x07);
LcdWriteData(zhuansu[1]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[2]>9)
{
LcdWriteCom(0x80+0x08);
LcdWriteData(zhuansu[2]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x08);
LcdWriteData(zhuansu[2]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[3]>9)
{
LcdWriteCom(0x80+0x09);
LcdWriteData(zhuansu[3]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x09);
LcdWriteData(zhuansu[3]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[4]>9)
{
LcdWriteCom(0x80+0x0a);
LcdWriteData(zhuansu[4]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x0a);
LcdWriteData(zhuansu[4]+0x30); //将数值转换为该显示的ASCII码
}
//显示阈值
Limit[0]=max/10000;
Limit[1]=max%10000/1000;
Limit[2]=max%10000%1000/100;
Limit[3]=max%10000%1000%100/10;
Limit[4]=max%10;
if(Limit[0]>9)
{
LcdWriteCom(0xc0+0x06); //设置显示位置
LcdWriteData(0x37+Limit[0]); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x06);
LcdWriteData(Limit[0]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[1]>9)
{
LcdWriteCom(0xc0+0x07);
LcdWriteData(Limit[1]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x07);
LcdWriteData(Limit[1]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[2]>9)
{
LcdWriteCom(0xc0+0x08);
LcdWriteData(Limit[2]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x08);
LcdWriteData(Limit[2]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[3]>9)
{
LcdWriteCom(0xc0+0x09);
LcdWriteData(Limit[3]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x09);
LcdWriteData(Limit[3]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[4]>9)
{
LcdWriteCom(0xc0+0x0a);
LcdWriteData(Limit[4]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x0a);
LcdWriteData(Limit[4]+0x30); //将数值转换为该显示的ASCII码
}
duty1[0]=duty2/100;
duty1[1]=duty2/10;
duty1[2]=duty2%10;
LcdWriteCom(0xc0+0x0d);
LcdWriteData(duty1[0]+0x30); //将数值转换为该显示的ASCII码
LcdWriteCom(0xc0+0x0e);
LcdWriteData(duty1[1]+0x30); //将数值转换为该显示的ASCII码
LcdWriteCom(0xc0+0x0f);
LcdWriteData(duty1[2]+0x30); //将数值转换为该显示的ASCII码
}
}
在三个中断里面,首先先说明外部中断。外部中断里面这个zhuansu2的意思就是在每触发一次下降沿就加1,清零则在timer0里面进行实现。在timer0里面,定义了一个暂态变量i,i累加到200以后执行一次if语句。由于我们单次定时的时间是10ms,所以执行200次以后就是2s钟。2s一到我们就将zhuansu2的值经过处理后赋值给zhuansu1,当然由于这个转速我们无法真正测量,所以就随便在后面乘以了一个2.82,显得有零有整也更加真实一点。同时清零zhuansu2和i,这样再下一个循环zhuansu2这个参量又可以通过外部中断累加。在定时器1里面呢则是输出PWM信号。由于定时器1的速度很快,为100us一次。所以最快可以100us改变一次IO口的输出电平。在里面设置了flag变量,flag变量的作用就是从0累加到200,当我们定义的duty,也就是占空比对应的数值大于flag的时候,就停止运行。当小于flag的时候,out就给高电平,out1就给低电平,这样保证电机处于正转的工作状态。那么想要电机反转也很简单,就是令out给低电平,out1给高电平,本质上就是通过左边电压高于右边或者左边电压低于右边电压来决定的。当我们定义的work_status参数为0时,我们默认不工作,故两个IO口都直接拉高。
/*******************************************************************************
* 函 数 名 : Int1() interrupt 2
* 函数功能 : 外部中断1的中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int1() interrupt 2 //外部中断1的中断函数
{
zhuansu2++;
}
/*******************************************************************************
* 函 数 名 : void Timer0() interrupt 1
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0() interrupt 1
{
static u16 i;
TH0=0XD8; //给定时器赋初值,定时10ms
TL0=0XF0;
i++;
if(i==200)
{
zhuansu1=zhuansu2*30*2.82;
zhuansu2=0;
i=0;
}
}
/*******************************************************************************
* 函 数 名 : void Timer1() interrupt 3
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1() interrupt 3
{
TH1=0xFF;
TL1=0x9C;//定时100us
flag++;
if(flag>199)
{
flag=1;
}
if(work_status==1)
{
if(flag<=duty)
{
out=1;
out1=0;
}
else
{
out=1;
out1=1;
}
}
if(work_status==0)
{
out=1;
out1=1;
}
}
总代码main.c,像LCD的代码和52的代码都是普中科技通用的,copy粘贴一下就行,请注意LCD三个数据口的连接方式,根据板子不同记得更改数据口的顺序。
#include<reg51.h>
#include"lcd.h"
#define uchar unsigned char
#define uint unsigned int
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
void DelayMs(unsigned int );
/*******************************************************************************
* 函数名 : 变量定义
* 函数功能 : 新的变量
* 输入 : 无
* 输出 : 无
*******************************************************************************/
unsigned char Limit[5]; //报警阈值存放位置
unsigned int max=10000; //设置报警阈值
unsigned char duty1[3]; //报警阈值存放位置
unsigned char zhuansu[5]; //转速存放位置
unsigned int zhuansu1=0; //显示转速大小
unsigned int zhuansu2=0; //显示转速大小
unsigned char code wenzi1[14]={"now_V: m/h"};
unsigned char code wenzi2[16]={"Limit: m "};
uint flag=0;
u16 duty=100; //占空比
u16 duty2=0;
u16 work_status=0; //工作状态,为0的时候不工作,为1的时候工作
sbit beep=P1^6;
sbit led=P1^5;
sbit out=P3^4; //PWM输出用于正传
sbit out1=P3^7; //PWM输出用于反转
sbit duty_add=P1^0; //占空比加1
sbit duty_reduce=P1^1; //占空比减一
sbit limit_add=P1^2; //阈值加1
sbit limit_reduce=P1^3; //阈值减1
sbit start=P1^4; //开始或者停止工作
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
while(i--);
}
/*******************************************************************************
* 函 数 名 : keypros
* 函数功能 : 按键处理函数,判断按键K1是否按下
*******************************************************************************/
void keypros()
{
if(duty_add==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(duty_add==0) //再次判断按键是否按下
{
duty+=1;
}
while(!duty_add); //检测按键是否松开
}
if(duty_reduce==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(duty_reduce==0) //再次判断按键是否按下
{
duty-=1;
}
while(!duty_reduce); //检测按键是否松开
}
if(limit_add==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(limit_add==0) //再次判断按键是否按下
{
max+=100;
}
while(!limit_add); //检测按键是否松开
}
if(limit_reduce==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(limit_reduce==0) //再次判断按键是否按下
{
max-=100;
}
while(!limit_reduce); //检测按键是否松开
}
if(start==0) //检测按键K1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(start==0) //再次判断按键是否按下
{
work_status+=1;
if(work_status>=2)work_status=0;
}
while(!start); //检测按键是否松开
}
}
/*******************************************************************************
* 函数名 : Int1Init()
* 函数功能 : 霍尔传感器接收信号
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Int1Init()
{
IT1=1;//下降沿触发
EX1=1;//打开中断0允许
EA=1; //打开总中断
}
/*******************************************************************************
* 函 数 名 : Timer0Init
* 函数功能 : 定时器0初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0Init()
{
TMOD|=0X11;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0XD8; //给定时器赋初值,定时10ms
TL0=0XF0;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
/*******************************************************************************
* 函 数 名 : Timer1Init
* 函数功能 : 定时器1初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1Init()
{
TMOD|=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
TH1=0XFF; //给定时器赋初值,定时100us
TL1=0X9C;
ET1=1;//打开定时器1中断允许
EA=1;//打开总中断
TR1=1;//打开定时器
}
/*******************************************************************************
* 函数名 : main
* 函数功能 : 主函数
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void main()
{
unsigned char i;
beep=1; //蜂鸣器不叫
led=0;
Int1Init(); //初始化霍尔传感器外部中断1
Timer0Init(); //定时器0初始化
Timer1Init(); //定时器1初始化
LcdInit();
LcdWriteCom(0x80);
for(i=0;i<14;i++)
{
LcdWriteData(wenzi1[i]);
}
LcdWriteCom(0x80+0x40);
for(i=0;i<16;i++)
{
LcdWriteData(wenzi2[i]);
}
while(1)
{
duty2=duty/2;
if(zhuansu1>=max)beep=0,led=1;
else beep=1,led=0;
//按键处理
keypros();
//显示转速
zhuansu[0]=zhuansu1/10000;
zhuansu[1]=zhuansu1%10000/1000;
zhuansu[2]=zhuansu1%10000%1000/100;
zhuansu[3]=zhuansu1%10000%1000%100/10;
zhuansu[4]=zhuansu1%10;
if(zhuansu[0]>9)
{
LcdWriteCom(0x80+0x06); //设置显示位置
LcdWriteData(0x37+zhuansu[0]); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x06);
LcdWriteData(zhuansu[0]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[1]>9)
{
LcdWriteCom(0x80+0x07);
LcdWriteData(zhuansu[1]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x07);
LcdWriteData(zhuansu[1]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[2]>9)
{
LcdWriteCom(0x80+0x08);
LcdWriteData(zhuansu[2]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x08);
LcdWriteData(zhuansu[2]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[3]>9)
{
LcdWriteCom(0x80+0x09);
LcdWriteData(zhuansu[3]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x09);
LcdWriteData(zhuansu[3]+0x30); //将数值转换为该显示的ASCII码
}
if(zhuansu[4]>9)
{
LcdWriteCom(0x80+0x0a);
LcdWriteData(zhuansu[4]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0x80+0x0a);
LcdWriteData(zhuansu[4]+0x30); //将数值转换为该显示的ASCII码
}
//显示阈值
Limit[0]=max/10000;
Limit[1]=max%10000/1000;
Limit[2]=max%10000%1000/100;
Limit[3]=max%10000%1000%100/10;
Limit[4]=max%10;
if(Limit[0]>9)
{
LcdWriteCom(0xc0+0x06); //设置显示位置
LcdWriteData(0x37+Limit[0]); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x06);
LcdWriteData(Limit[0]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[1]>9)
{
LcdWriteCom(0xc0+0x07);
LcdWriteData(Limit[1]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x07);
LcdWriteData(Limit[1]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[2]>9)
{
LcdWriteCom(0xc0+0x08);
LcdWriteData(Limit[2]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x08);
LcdWriteData(Limit[2]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[3]>9)
{
LcdWriteCom(0xc0+0x09);
LcdWriteData(Limit[3]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x09);
LcdWriteData(Limit[3]+0x30); //将数值转换为该显示的ASCII码
}
if(Limit[4]>9)
{
LcdWriteCom(0xc0+0x0a);
LcdWriteData(Limit[4]+0x37); //将数值转换为该显示的ASCII码
}
else
{
LcdWriteCom(0xc0+0x0a);
LcdWriteData(Limit[4]+0x30); //将数值转换为该显示的ASCII码
}
duty1[0]=duty2/100;
duty1[1]=duty2/10;
duty1[2]=duty2%10;
LcdWriteCom(0xc0+0x0d);
LcdWriteData(duty1[0]+0x30); //将数值转换为该显示的ASCII码
LcdWriteCom(0xc0+0x0e);
LcdWriteData(duty1[1]+0x30); //将数值转换为该显示的ASCII码
LcdWriteCom(0xc0+0x0f);
LcdWriteData(duty1[2]+0x30); //将数值转换为该显示的ASCII码
}
}
/*******************************************************************************
* 函 数 名 : Int1() interrupt 2
* 函数功能 : 外部中断1的中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int1() interrupt 2 //外部中断1的中断函数
{
zhuansu2++;
}
/*******************************************************************************
* 函 数 名 : void Timer0() interrupt 1
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer0() interrupt 1
{
static u16 i;
TH0=0XD8; //给定时器赋初值,定时10ms
TL0=0XF0;
i++;
if(i==200)
{
zhuansu1=zhuansu2*30*2.82;
zhuansu2=0;
i=0;
}
}
/*******************************************************************************
* 函 数 名 : void Timer1() interrupt 3
* 函数功能 : 定时器0中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Timer1() interrupt 3
{
TH1=0xFF;
TL1=0x9C;//定时100us
flag++;
if(flag>199)
{
flag=1;
}
if(work_status==1)
{
if(flag<=duty)
{
out=1;
out1=0;
}
else
{
out=1;
out1=1;
}
}
if(work_status==0)
{
out=1;
out1=1;
}
}
实测效果还不错,可以用来实战演练一番。
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源
我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n
我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:
文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3
我有33个规范以大约5秒的速度运行,以这种速度运行会导致测试套件变慢。我追踪到请求规范(4秒以上),因为模型规范只用了一小部分时间。我已经检查过,我的请求规范没有任何过于复杂或不必要的东西,所以我不知道该去哪里让它们更快,而不是只在推送代码之前运行它们以确保一切正常.加快请求规范的最佳方法是什么? 最佳答案 我使用Spork来加速我的测试。它保持整个环境加载以赢得时间。看看这个博客:http://ykyuen.wordpress.com/2010/12/14/rails-running-rspec-with-spork-test-s
我正在尝试整个BDD方法并想测试AMQP基于Vanilla的方面Ruby我正在写的应用程序。选择Minitest后作为与其他名副其实的蔬菜框架不同的平衡功能和表现力的测试框架,我着手编写此规范:#File./test/specs/services/my_service_spec.rb#Requirementsfortestrunningandconfigurationrequire"minitest/autorun"require"./test/specs/spec_helper"#Externalrequires#MinitestSpecsforEventMachinerequire