51单片机物联网智能小车系列文章目录
第一篇:最简单DIY的51蓝牙遥控小车设计方案
第二篇:最简单DIY串口蓝牙硬件实现方案
第三篇:最简单DIY蓝牙PS2遥控器控制蓝牙智能小车
第四篇:最简单DIY基于51单片机的舵机控制器
第五篇:最简单DIY基于蓝牙、51单片机和舵机的钢铁爱国者机关枪控制器
第六篇:最简单DIY基于Android系统的万能蓝牙设备智能遥控器
第七篇:最简单DIY基于51单片机、PCA9685、IIC、云台的舵机集群控制程序
第八篇:最简单DIY基于C#和51单片机上下位机一体化的PCA9685舵机控制程序
文章目录
前言
daodanjishui物联网核心原创技术之最简单DIY基于C#和51单片机上下位机一体化的PCA9685舵机控制程序。
市面上有各种开源智能舵机集群上位机软件,但是有复杂的有简单的,如果想快速入门51单片机使用C#上位机控制sg90舵机,这个方案会给你一个快捷高效的方案。
一、最简单DIY基于C#和51单片机上下位机一体化的PCA9685舵机控制程序是什么?
在第七篇完成了51单片机使用PCA9685模块控制多个舵机的设计,读者好奇当时为什么不写控制多个舵机UI界面呢?那是因为学习是一个循序渐进的过程,掌握基本的技能才能去举一反三。到现在来看,我第八篇博文是第七篇博文的升级版,可以使用同一个51单片机控制两个舵机云台,并且互相不受影响,一个C#UI界面通过串口控制一个舵机云台,一个舵机云台由51单片机自动控制执行相同循环的动作。舵机控制代码相当精妙,部分是自己的原创代码,如果不好好专研深入思考,是不可能写出这样的代码的,物联网技术就需要万物互联,那能不能用一个UI界面来控制多个舵机呢?答案是肯定的,这次需要用到PCA9685模块来直接控制舵机,51单片机通过I2C总线控制PCA9685模块,还要用到VS2010开发C# 程序形成UI界面,包括前端和后端的代码。
第四篇是用定时器0控制一个舵机,第五篇是用定时器0控制两个舵机;它们都是用定时器0产生并控制PWM波最终控制舵机的;现在不再用定时器0直接控制舵机了,用I2C总线来操控PCA模块来控制舵机,全方位开源原创,现在用文字的形式记录下来,这次源码进行了大幅度的升级,代码写得非常精简和奇妙,现在用文字的形式记录下来,对自己童年时代深刻地回忆和对未来技术的展望。“闭门造UI”虽然有点痛苦,但是当你看到自己亲手做的舵机在上电后通过UI界面受控跑起来的时候,你会发现一切的付出都是值得的!全家福如下图所示:
作品优酷视频演示地址:https://v.youku.com/v_show/id_XNTAxNDQ4NTIwNA==.html
直接观看视频
最简单DIY基于C#和51单片机上下位机一体化的PCA9685舵机控制程序
二、原理分析
1.现有技术
这一次我打算采用舵机控制模块来间接控制舵机,很多老外的arduino项目都是用PCA9685来控制舵机的,在电路城很多的卖家都采用这个arduino库来控制舵机,优点有几个:1.直接调用现成的库,如下图所示:
2.直接用arduino集成的IIC的库控制IIC总线直接控制PCA9685,非常方便,配置几个参数就可以搞定多个舵机控制了。 那么我就隆重介绍一下缺点:
(1)51单片机不能使用这些库,stm32也不能用这些库,所以电路城卖的都是免费的arduino舵机程序为主,含金量不是很高。(2)就算可以使用arduino单片机,这个舵机响应速度还是比较慢的,如果我用stm32来控制舵机,会发现流畅很多,几十个舵机一起用都没有压力。(3)网上用51单片机移植Adafruit-PWM-Servo-Driver-Library 的arduino库到51单片机上成功的代码不多,也没有详细注释和说明。
2.我使用的技术
我用了这个PCA模块一共控制了四个舵机,组合成两个云台。视频中可以看到,这两个云台动作是一样的,先转0度再转60度最后转到80,一直循环下去,因为我在程序中写了一个按照时间顺序来实现彩排效果的舵机动作操,读者可以学到我是怎么按照时间顺序来控制一堆舵机的,绝对够简单暴力,程序简单注释详细老少皆宜。不买云台单单控制舵机也是没有问题的!
代码说明:这次代码不拖泥带水,直接用51单片机的普通IO口模拟IIC通信协议实现替代arduino的IIC通信库程序,移植arduino的舵机控制库到51单片机上使用,代码含金量不低,除了可以学会如何使用IIC总线之外,还能学会用51控制PCA9685模块从而控制多个舵机。下面是代码的截图:
3.软硬件准备
硬件说明:这次的材料比前两篇博文不一样的地方,多了PCA9685,云台可以不用买,反正我们的重点是控制舵机嘛,某宝上可以十块钱左右买到。舵机的结构和参数如下:
软件说明:那么这次真的加入C#上位机通过串口来控制两个舵机,另外两个舵机交给死循环重复做相同的动作,还有一个特色就是,可以通过上位机发送一个角度,那么两个受控舵机中的一个会转到指定的角度上,非常有趣。
二、程序设计
1.设计软件逻辑
(1)其中有两个舵机采用自动化流水重复执行同一个动作,不需要外加控制指令。那么就需要在死循环一直执行某段程序。
while(1){
/*
setPWM(0, 0, SERVO000);//舵机0先转到0度
delayms(1000);//延时1S
setPWM(0, 0, 239);//就是让舵机0转到60度
delayms(1000);//延时1S
setPWM(0, 0, SERVO80);//#define SERVO80 284 //80度
delayms(1000);//延时1S
setPWM(1, 0, SERVO000);//舵机1先转到0度
delayms(1000);//延时1S
setPWM(1, 0, 239);//就是让舵机1转到60度
delayms(1000);//延时1S
setPWM(1, 0, SERVO80);//#define SERVO80 284 //80度
delayms(1000);//延时1S
*/
//如果需要控制其他的舵机,可以以此类推,呵呵,注意了一个PCA9685最多可以控制16个舵机
setPWM(14, 0, SERVO000);//舵机14先转到0度
delayms(1000);//延时1S
setPWM(14, 0, 239);//就是让舵机14转到60度
delayms(1000);//延时1S
setPWM(14, 0, SERVO80);//#define SERVO80 284 //80度
delayms(1000);//延时1S
setPWM(15, 0, SERVO000);//舵机1先转到0度
delayms(1000);//延时1S
setPWM(15, 0, 239);//就是让舵机1转到60度
delayms(1000);//延时1S
setPWM(15, 0, SERVO80);//#define SERVO80 284 //80度
delayms(1000);//延时1S
//这里编写按键扫描之类的函数
}
(2)复制现成的51单片机I2C通信代码,网上很多的。
/*---------------------------------------------------------------
IIC初始化
----------------------------------------------------------------*/
void init()
{
sda=1; //sda scl使用之前被拉高
delayus();
scl=1;
delayus();
}
/*---------------------------------------------------------------
IIC总线开始传输
----------------------------------------------------------------*/
void start()
{
sda=1;
delayus();
scl=1; //scl高sda拉低
delayus();
sda=0;
delayus();
scl=0;
delayus();
}
/*---------------------------------------------------------------
IIC结束
----------------------------------------------------------------*/
void stop()
{
sda=0;
delayus();
scl=1; //scl拉高 sda产生上升沿
delayus();
sda=1;
delayus();
}
/*---------------------------------------------------------------
IIC应答
----------------------------------------------------------------*/
void ACK()
{
uchar i;
scl=1;
delayus();
while((sda=1)&&(i<255))
i++;
scl=0;
delayus();
}
/*---------------------------------------------------------------
写一个字节
----------------------------------------------------------------*/
void write_byte(uchar byte)
{
uchar i,temp;
temp=byte;
for(i=0;i<8;i++)
{
temp=temp<<1;
scl=0;
delayus();
sda=CY;
delayus();
scl=1;
delayus();
}
scl=0;
delayus();
sda=1;
delayus();
}
/*---------------------------------------------------------------
读一个字节
----------------------------------------------------------------*/
uchar read_byte()
{
uchar i,j,k;
scl=0;
delayus();
sda=1;
delayus();
for(i=0;i<8;i++)
{
delayus();
scl=1;
delayus();
if(sda==1)
{
j=1;
}
else j=0;
k=(k<< 1)|j;
scl=0;
}
delayus();
return k;
}
(3)根据I2C协议组合成PCA9685驱动程序
/*---------------------------------------------------------------
给PCA9685写字节
----------------------------------------------------------------*/
void PCA9685_write(uchar address,uchar date)
{
start();
write_byte(PCA9685_adrr); //PCA9685
ACK();
write_byte(address); //
ACK();
write_byte(date); //
ACK();
stop();
}
/*---------------------------------------------------------------
从PCA9685读一个字节
----------------------------------------------------------------*/
uchar PCA9685_read(uchar address)
{
uchar date;
start();
write_byte(PCA9685_adrr); //PCA9685
ACK();
write_byte(address);
ACK();
start();
write_byte(PCA9685_adrr|0x01); //
ACK();
date=read_byte();
stop();
return date;
}
/*---------------------------------------------------------------
PCA9685复位
----------------------------------------------------------------*/
void reset(void)
{
PCA9685_write(PCA9685_MODE1,0x0);
}
void begin(void)
{
reset();
}
(4)PCA9685参数设置
/*---------------------------------------------------------------
PCA9685参数设置
----------------------------------------------------------------*/
void setPWMFreq(float freq)
{
uint prescale,oldmode,newmode;
float prescaleval;
freq *= 0.92; // Correct for overshoot in the frequency setting
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval + 0.5);
oldmode = PCA9685_read(PCA9685_MODE1);
newmode = (oldmode&0x7F) | 0x10; // sleep
PCA9685_write(PCA9685_MODE1, newmode); // go to sleep
PCA9685_write(PCA9685_PRESCALE, prescale); // set the prescaler
PCA9685_write(PCA9685_MODE1, oldmode);
delayms(2);
PCA9685_write(PCA9685_MODE1, oldmode | 0xa1);
}
/*---------------------------------------------------------------
PWM波参数设置
num:舵机的序号0~15一共16个舵机
on:PWM上升计数值的范围是0到4096;
off:PWM下降计数的范围是从0到4096;
一个PWM周期分成4096份开始计数,从0开始计算到on个数的时候就是输出高电平
计算到off个数的时候就变成低电平,所以on不等于0的时候就是延时,等于0的时候
off的值与4096的比值就是占空比。
----------------------------------------------------------------*/
void setPWM(uint num, uint on, uint off)
{
PCA9685_write(LED0_ON_L+4*num,on);
PCA9685_write(LED0_ON_H+4*num,on>>8);
PCA9685_write(LED0_OFF_L+4*num,off);
PCA9685_write(LED0_OFF_H+4*num,off>>8);
}
(5)因为有两个舵机需要用到C#UI界面发送指令控制,所以需要编写单片机串口中断程序,这个代码是自己原创的,包括字符串接收和解析实现角度的翻转,值得大家去借鉴一下。
void receive() interrupt 4 //串口中断服务程序,单片机串口收到数据就会触发?
{
if(RI==1){
RI=0; //重新清0等待接收
a=SBUF;//接收电脑传输过来的数据
//SBUF=a;//单片机把接收到的数据返回给电脑
//这里可以放置舵机中断服务程序需要处理的逻辑
//下面加入智能小车指令解析
AA[i++]=a;
if((AA[i-1]=='\n')&&(AA[i-2]=='\r')){//判断是否遇到了"\r\n"
AA[i-2]='\0'; //用"\0"替代了"\r"的字符变量
if((strstr(AA,"FFF")!=0)){//使用了字符串查找函数,这是第二种方法
send(AA);
angle1=angle1+add_angle;
if(angle1>=180)
angle1=180;
sprintf(BB,"data is %d",angle1);
send(BB);
setPWM(0, 0, (int)(4096*(0.5+((float)angle1)*(2.5-0.5)/180))/20);//就是让舵机0转到angle1度
//front();//前进
}else if((strstr(AA,"BBB")!=0)){//使用了字符串查找函数,这是第二种方法
send(AA);
angle1=angle1-add_angle;
if(angle1<=0)
angle1=0;
sprintf(BB,"data is %d",angle1);
send(BB);
setPWM(0, 0, (int)(4096*(0.5+((float)angle1)*(2.5-0.5)/180))/20);//就是让舵机0转到angle1度
//back();//后退
}else if((strstr(AA,"LLL")!=0)){//使用了字符串查找函数,这是第二种方法
send(AA);
angle2=angle2-add_angle;
if(angle2<=0)
angle2=0;
sprintf(BB,"data is %d",angle2);
send(BB);
setPWM(1, 0, (int)(4096*(0.5+((float)angle2)*(2.5-0.5)/180))/20);//就是让舵机1转到angle2度
//left();//左转
}else if((strstr(AA,"RRR")!=0)){//使用了字符串查找函数,这是第二种方法
send(AA);
angle2=angle2+add_angle;
if(angle2>=180)
angle2=180;
sprintf(BB,"data is %d",angle2);
send(BB);
setPWM(1, 0, (int)(4096*(0.5+((float)angle2)*(2.5-0.5)/180))/20);//就是让舵机1转到angle2度
//right();//右转
}else {
send(AA);
angle=atoi(AA);
if(angle>=0&&angle<=180){
setPWM(0, 0, (int)(4096*(0.5+((float)angle)*(2.5-0.5)/180))/20);//就是让舵机0转到angle度
/*
bai=angle/100;
shi=angle%100/10;
ge=angle%10;
*/
//sprintf(BB,"data is %f",(4096*(0.5+((float)angle)*(2.5-0.5)/180))/20);
sprintf(BB,"data is %d",angle);
send(BB);
//stop();//停止
}
}
i=0;
for(j=0;j<10;j++){
AA[j]='\0';
}
//逻辑结束
TI=0; //清除发送中断标志位
}
}
}
(6)C#设计的工程和代码截图
缺省代码说明:整个Keil5工程一个文件,非常简洁,很适合二次开发或者学习,目前给出了所有的函数,还剩下一些变量的定义和宏定义,如果耐心够的读者慢慢看也能收获很多,说不定还能再现我这个程序出来,毕竟代码关键的地方都写了注释,这个代码我编写了一周,还有就是C#的工程代码其实就是用了串口调试助手的代码修改的,网上有一大堆,我就不放出来了。如果感兴趣的读者想要我全部的源码,麻烦到我指定的地方下载工程吧,尊重原创,尊重劳动成果,我能保证的是我代码是靠谱的,能控制所指定的硬件,视频为证。
2.分析代码
首先强调一下这个程序使用MDK5和VS2010写的,如果还有其他需求请联系我,其实用什么软件开发程序都是次要的,关键是你设计产品的逻辑。记得我的老师说过,其实开发语言只是工具而已,关键还是你自己心中是否有完整的运行逻辑机制。
其实给出的源码我写上了完整的注释了,就说说读者能学到什么内容吧?这份源码可以学习的地方很多的,比如I2C协议,舵机协同控制,PCA9685模块的使用,C#串口通信,C#UI界面设计等等,如果要实现UI界面控制多个舵机,那么我这个源码绝对物超所值!
三、仿真与调试
这个交给视频演示。比较简单就不详细说明了。
下面是运行控制舵机的截图:
总结
功能描述:用VS2010编写的C#上位机用四个按键控制两个舵机构成的云台运动,连接采用的是串口通信的方式(所以看到我把单片机的P3.0和P3.1接到USB转TTL的线上最后接到电脑上),另外在上位机的输入框可以输入0到180的整数,点击单次发送,受按键控制的两个舵机中一个舵机响应输入的角度值并转到响应的角度上。另外两个舵机构成的云台不受上位机的控制,自己在重复我提前输入的动作周而复始地运动。一个受控,一个不受控相当有意思。
知识描述:这次作品涉及到的知识点很多,比如单片机串口通信,单片机串口指令解析、串口数字解析、单片机IO口模拟IIC通信、PCA9685arduino库函数移植到51单片机上,VS2010 C#串口控制程序的上位机开发。知识相当给力,上位机可以根据买家需要定制的logo修改成想要的效果。还有很多可以提高的地方。
用C# 写的上位机在windows操作系统上运行的兼容性是很好的,因为C#是由Microsoft开发的,开发包很小,不像用java开发的上位机,占的体积大,还要安装jdk才能运行,否则要打包压缩,据我估计匿名四轴的上位机也是用C#开发的,但是人家不开源啊,学好我这个C#开发的开源上位机的,还是有点用处的。注意了我是用VS2010来开发的,软件兼容问题请买家自行调整!单片机下位机的源码请看上一期的截图,这次修改就不截图了,用MDK5写的51程序。
现代生活中UI界面原来越多,所以嵌入式UI设计开发永不停止,JAVA SWING编写的界面也很广泛,那么敬请期待,下次我将使用C#上位机结合摄像头,将单片机采集摄像头的图片传输给C#上位机,可以采用有线也可以采用无线,实现一个远程图像监控舵机系统。精彩值得期待!
代码工程下载链接:https://gf.bilibili.com/item/detail/1107738114
点我直接跳转
PCA9685参考资料