单片机、智能小车套装(电机、电机驱动、电源、车轮、杜邦线)
接通VCC,GND 模块电源指示灯亮,以下资料来源官方,官方给的数据有误。(实际要通过自己代码来调试电机正转反转)
IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;
IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;
IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;
IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;
- #include "reg52.h"
- sbit RightCon1A = P3^2; //右轮
- sbit RightCon1B = P3^3;
- sbit LeftCon1A = P3^4; //左轮
- sbit LeftCon1B = P3^5;
-
- //前进
- void goForward()
- {
- LeftCon1A = 0; //左轮向前
- LeftCon1B = 1;
-
- RightCon1A = 0; //右轮向前
- RightCon1B = 1;
- }
- //后退
- void goBack()
- {
- LeftCon1A = 1;
- LeftCon1B = 0;
-
- RightCon1A = 1;
- RightCon1B = 0;
- }
- //左转
- void goLeft()
- {
- LeftCon1A = 0; //左轮不动
- LeftCon1B = 0;
-
- RightCon1A = 0; //右轮向前
- RightCon1B = 1;
- }
- //右转
- void goRight()
- {
- LeftCon1A = 0; //左轮向前
- LeftCon1B = 1;
-
- RightCon1A = 0; //右轮不动
- RightCon1B = 0;
- }
-
- #include "reg52.h"
- #include "intrins.h"
- #include "motor.h"
-
- void Delay1000ms() //@11.0592MHz
- { unsigned char i, j, k;
- _nop_();
- i = 8;
- j = 1;
- k = 243;
- do
- {
- do
- {
- while (--k);
- } while (--j);
- } while (--i);
- }
-
- void main()
- {
- while(1){
- goForward();
- Delay1000ms();
- Delay1000ms();
- goBack();
- Delay1000ms();
- Delay1000ms();
- goLeft();
- Delay1000ms();
- Delay1000ms();
- goRight();
- Delay1000ms();
- Delay1000ms();
- }
- }
-
-
配置串口中断模式,串口不断的接收数据,如果串口接收到我们自己设置【标记】,让小车进行相对应的操作。
直接将自己之前做的串口接收数据的代码复制粘贴过来 (下面也有附代码),串口初始化和串口发送和接收的代码基本通用的,不用改。
1.配置串口通信中断模式(避免与主函数main执行的程序发生冲突);
2.每次接收到数据都会进行串口中断。如果接收到"M"开头的数据,把数组下标"i"设置为0,让数组从下标0开始保存数据;
3.判断数组存的数据进行相对应的操作;
4.串口处于一直接收数据的状态,如果一直没有接收到我们需要的M开头的数据,存数据的数组慢了(越界了),要记得把数组清0;
5.使用STC-ISP软件的串口助手调试(如下图)。
重点:
①要记得配置串口工作方式1,REN位要使能可以接收数据
②打开串口中断,用串口中断来接收数据
③下面有附上串口通信UART的代码
- #include "reg52.h"
- #include "motor.h"
- #include "string.h"
-
- #define SIZE 12
- sfr AUXR = 0x8E;
- char buffer[SIZE];
-
- void UartInit(void) //9600bps@11.0592MHz
- {
- AUXR = 0x01;
- SCON = 0x50; //配置串口工作方式1,REN使能接收
- TMOD &= 0x0F;
- TMOD |= 0x20;//定时器1工作方式位8位自动重装
-
- TH1 = 0xFD;
- TL1 = 0xFD;//9600波特率的初值
- TR1 = 1;//启动定时器
-
- EA = 1;//开启总中断
- ES = 1;//开启串口中断
- }
-
- //M1前 M2后 M3左 M4右
- void Uart_Handler() interrupt 4
- {
- static int i = 0;//静态变量,被初始化一次
- char tmp;
-
- if(RI)//中断处理函数中,对于接收中断的响应
- {
- RI = 0; //清除接收中断标志位
-
- tmp = SBUF; //①获取SBUF寄存器接收到的数据
- if(tmp == 'M'){ //②如果接收数据里有字母M,就让buffer数组从0开始把
- i = 0;
- }
- //③然后buffer[0]把M存起来,
- //i加加, buffer[1]存接下来的数据
- buffer[i++] = tmp; //buffer[i]=tmp; i++;
-
- //④buffer[0]=M的情况下,buffer[1]为1,2,3,4做不同的操作
- if(buffer[0] == 'M'){
- switch(buffer[1]){
- case '1':
- goForward();
- break;
- case '2':
- goBack();
- break;
- case '3':
- goLeft();
- break;
- case '4':
- goRight();
- break;
- default:
- stop();
- break;
- }
- }
-
- //如果接收数据一直没有遇到M,数组满了,把数组清空
- if(i == 12) {
- memset(buffer, '\0', SIZE);
- i = 0;
- }
- }
-
- }
-
1.把蓝牙RX、TX、GND、VCC插上小车的单片机,
2.然后手机微信扫描蓝牙模块自带的蓝牙助手(或者自己开发的手机APP有蓝牙连接功能),
3.选择蓝牙的名字连接上,发送文本数据即可。
【蓝牙的知识点】:
1.因为蓝牙是透明传输,不对数据进行任何的解析和处理,只负责将数据从源地址传原封不动输到目的地址。
2.所以蓝牙
需要接收数据就直接插上RX口,
需要发送数据就直接插上TX口即可。
3.AT指令
可以通过AT指令来改蓝牙名字,重启模块等等操作。具体的指令,模块的官网有。
把小车的效果做的跟遥控小车一样,比如不按它就不动,一直按着前进的按键它才会向前。
1.在主函数循环执行stop()函数,让小车一直保持停止的状态
2.那么当有人按下前进按键,串口接收到数据后中断主函数来执行串口中断中的前进函数,一直按着前进的按键就一直中断,一直往前。如果松手,就会停止。
虽然我们一直按的前进,单片机串口也不断地接收到了前进的指令,并执行前进的程序,但是这过程太耗时了。
每一次的单片机接收到的前进信号是我们手机按键按下前进按钮,然后手机的蓝牙把数据发送到单片机的蓝牙,单片机的蓝牙再传输给单片机的串口,串口接收到数据后传给单片机的cpu,单片机才执行串口中断并进行前进的操作。
所以单片机接收到前进信号的间隔太久,虽然我们一直按着前进,但是等到单片机接收到下一次的前进信号这中间的间隔时间,由于单片机执行的速度非常的快,例如51单片机的12MHZ,执行一条指令的机器周期只需要1us起步,所以单片机等接收到你下一次漫长的前进命令时,单片机早就又执行了几千上万次的主函数中的stop()函数。
所以虽然我们一直按的前进,但是小车根本不会动,你一直按的前进,单片机串口刚接收到小车前进的指令并执行,还看不出啥反应,单片机主函数马上又来了几千上万条停止的指令。
在串口中断的小车执行前后左右的操作后面延时一会(这里延时10ms),让它先看到一点前后左右操作。具体延时多久,这个时间自己慢慢调试,找到一个平衡点。
- #include "reg52.h"
- #include "motor.h"
- #include "string.h"
- #include "delay.h"
- sbit D5 = P3^7;
- #define SIZE 12
-
- sfr AUXR = 0x8E;
- char buffer[SIZE];
-
- void UartInit(void) //9600bps@11.0592MHz
- {
- AUXR = 0x01;
- SCON = 0x50; //配置串口工作方式1,REN使能接收
- TMOD &= 0x0F;
- TMOD |= 0x20;//定时器1工作方式位8位自动重装
-
- TH1 = 0xFD;
- TL1 = 0xFD;//9600波特率的初值
- TR1 = 1;//启动定时器
-
- EA = 1;//开启总中断
- ES = 1;//开启串口中断
- }
-
- //M1qian M2 hou M3 zuo M4 you
- void Uart_Handler() interrupt 4
- {
- static int i = 0;//静态变量,被初始化一次
- char tmp;
-
- if(RI)//中断处理函数中,对于接收中断的响应
- {
- RI = 0;//清除接收中断标志位
- tmp = SBUF;
- if(tmp == 'M'){
- i = 0;
- }
- buffer[i++] = tmp;
-
- //小车前后左右指令
- if(buffer[0] == 'M'){
- switch(buffer[1]){
- case '1':
- goForward();
- Delay10ms();
- break;
- case '2':
- goBack();
- Delay10ms();
- break;
- case '3':
- goLeft();
- Delay10ms();
- break;
- case '4':
- goRight();
- Delay10ms();
- break;
- default:
- stop();
- break;
- }
- }
-
- if(i == 12) {
- memset(buffer, '\0', SIZE);
- i = 0;
- }
- }
- }
-
- #include "motor.h"
- #include "delay.h"
- #include "uart.h"
-
- void main()
- {
- UartInit();
-
- while(1){
- stop();
- }
- }
-
- PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调
- 制,等效出所需要的波形 (包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占
- 空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个
- 信号周期的百分比,例如方波的占空比就是50%.
-
①脉冲宽度调制
②通过占空比编码模拟信号
③占空比:一个周期内,高电平占据时长的百分比(通俗点就是有效的数据占空的比例)
【自己的理解】
如下图,
绿色部分一个周期是20ms,在一个周期内,高电平占0.5ms,低电平占19.5ms。占空比是:0.5ms/20ms =2.5%。
红色部分一个周期是20ms,在一个周期内,高电平占2ms,低电平占18ms。占空比是:2ms/20ms=10%。
这个高、低电平在每个周期里的持续时间我们可以自己调制,这就是脉冲宽度调制,这就是PWM。
按照上面的波形,我们可以用定时器来设置20ms为一个周期,设置0.5ms为一个单位(也可以设置1ms、2ms为一个单位,看个人),
按照这个设置,我们定时器计数0.5ms就进入定时器中断函数一次,每次进去统计次数+1,进去定时器中断函数40次的时候就是一个周期也就是20ms。
ok,我们用定时器模拟出了一个20ms的周期,接下来写代码模拟红色的波形图,在20ms内,我们给io口 2ms 的高电平,剩下18ms为低电平,然后以此为周期。
那用PWM给小车调速也是一样的原理,由于直接给电机电平是全速的状态,那我们可以通过pwm在一个周期内,一段时间给小车电平,一段时间不给电平,这样就可以很好的控制小车的速度。在不给电平的状态下,其实电机是有惯性的,也不会完全停止。
除了可以调速,通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是完全的不动,这样转弯会丝滑很多。(看下一章,pwm差速转弯)
- #include "motor.h"
- #include "reg52.h"
-
- char speed;
- char cnt = 0;
-
- void Time0Init()
- {
- //1. 配置定时器0工作模式位16位计时
- TMOD = 0x01;
- //2. 给初值,定一个0.5出来
- TL0=0x33;
- TH0=0xFE;
- //3. 开始计时
- TR0 = 1;
- TF0 = 0;
- //4. 打开定时器0中断
- ET0 = 1;
- //5. 打开总中断EA
- EA = 1;
- }
-
- void Time0Handler() interrupt 1
- {
- cnt++; //统计爆表的次数. cnt=1的时候,报表了1
- //重新给初值
- TL0=0x33;
- TH0=0xFE;
-
- //控制PWM波
- if(cnt < speed){
- //前进
- goForward();
- }else{
- //停止
- stop();
- }
-
- if(cnt == 40){//爆表40次,经过了20ms
- cnt = 0; //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
- }
- }
-
- #include "motor.h"
- #include "delay.h"
- #include "uart.h"
- #include "time.h"
-
- extern char speed;
-
- void main()
- {
- Time0Init();
- //UartInit();
-
- while(1){
- speed = 10; //10份单位时间0.5ms全速运行,30份停止,所以慢
- Delay1000ms();
- Delay1000ms();
- speed = 20; //20份单位时间0.5ms的全速运行,20份的0.5ms停止
- Delay1000ms();
- Delay1000ms();
- speed = 40; //全速前进
- Delay1000ms();
- Delay1000ms();
- }
- }
-
通过PWM调速这样更有利于小车的转弯。因为小车在转弯的过程中,需要一个轮子动,一个轮子不动。如果在小车直行的过程中,突然让一个轮子不动,让小车转弯,这样会显得转弯不够丝滑,通过pwm调速,可以让轮子的速度降下来,而不是让轮子完全的不动,这样转弯会丝滑很多。
原理还是跟第四章的PWM调速小车一样,由于需要把小车左右两边的轮子分开控制,所以我们需要两个定时器来分别模拟PWM来控制左右两边的电机。所以配置两个定时器和写两个中断函数即可,直接看代码。
- #include "motor.h"
- #include "reg52.h"
-
- char speedLeft;
- char cntLeft = 0;
-
- char speedRight;
- char cntRight = 0;
-
- void Time0Init()
- {
- //1. 配置定时器0工作模式位16位计时
- TMOD = 0x01;
- //2. 给初值,定一个0.5出来
- TL0=0x33;
- TH0=0xFE;
- //3. 开始计时
- TR0 = 1;
- TF0 = 0;
- //4. 打开定时器0中断
- ET0 = 1;
- //5. 打开总中断EA
- EA = 1;
- }
-
- void Time1Init()
- {
- //1. 配置定时器1工作模式位16位计时
- TMOD &= 0x0F; //保留低四位,定时器0的配置不变,把高四位清0
- TMOD |= 0x01 << 4; //0x01左移4位0x10,再或TMOD。
- //2. 给初值,定一个0.5出来
- TL1=0x33;
- TH1=0xFE;
- //3. 开始计时
- TR1 = 1;
- TF1 = 0;
- //4. 打开定时器1中断
- ET1 = 1;
- //5. 打开总中断EA
- EA = 1;
- }
-
-
- void Time1Handler() interrupt 3
- {
- cntRight++; //统计爆表的次数. cnt=1的时候,报表了1
- //重新给初值
- TL1=0x33;
- TH1=0xFE;
-
- //控制PWM波
- if(cntRight < speedRight){
- //右轮前进
- goForwardRight();
- }else{
- //右轮停止
- stopRight();
- }
-
- if(cntRight == 40){//爆表40次,经过了20ms
- cntRight = 0; //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
- }
-
- }
-
- void Time0Handler() interrupt 1
- {
- cntLeft++; //统计爆表的次数. cnt=1的时候,报表了1
- //重新给初值
- TL0=0x33;
- TH0=0xFE;
-
- //控制PWM波
- if(cntLeft < speedLeft){
- //左轮前进
- goForwardLeft();
- }else{
- //左轮停止
- stopLeft();
- }
-
- if(cntLeft == 40){//爆表40次,经过了20ms
- cntLeft = 0; //当40次表示20ms,重新让cnt从0开始,计算下一次的20ms
- }
- }
-
- #include "motor.h"
- #include "delay.h"
- #include "uart.h"
- #include "time.h"
-
- extern char speedLeft;
- extern char speedRight;
-
- void main()
- {
- Time0Init(); //先初始化定时器0,因为定时器0我们配置的比较暴力
- Time1Init(); //再初始化定时器1
- //UartInit();
-
- while(1){
- //向左转弯:右边轮子快,左边轮子慢
- speedLeft = 10;//10份单位时间全速运行,30份停止,所以慢,20ms是40份的500us
- speedRight = 40;
- Delay1000ms();
- Delay1000ms();
-
- //向右转弯:左边轮子快,右边轮子慢
- speedLeft = 40;
- speedRight = 10;
- Delay1000ms();
- Delay1000ms();
- }
- }
-
- #include "reg52.h"
-
- sbit RightCon1A = P3^2;
- sbit RightCon1B = P3^3;
-
- sbit LeftCon1A = P3^4;
- sbit LeftCon1B = P3^5;
-
-
- //①通过PWM来丝滑的左右转弯
- //左轮前进
- void goForwardLeft()
- {
- LeftCon1A = 0;
- LeftCon1B = 1;
- }
- //左轮停止
- void stopLeft()
- {
- LeftCon1A = 0;
- LeftCon1B = 0;
- }
- //右轮前进
- void goForwardRight()
- {
- RightCon1A = 0;
- RightCon1B = 1;
- }
- //右轮停止
- void stopRight()
- {
- RightCon1A = 0;
- RightCon1B = 0;
- }
-
- //————————————————————————————————
- //②全速的前进后退左转右转
- void goForward()
- {
- LeftCon1A = 0;
- LeftCon1B = 1;
-
- RightCon1A = 0;
- RightCon1B = 1;
- }
-
- void goRight()
- {
- LeftCon1A = 0;
- LeftCon1B = 1;
-
- RightCon1A = 0;
- RightCon1B = 0;
- }
-
-
- void goLeft()
- {
- LeftCon1A = 0;
- LeftCon1B = 0;
-
- RightCon1A = 0;
- RightCon1B = 1;
- }
-
- void goBack()
- {
- LeftCon1A = 1;
- LeftCon1B = 0;
-
- RightCon1A = 1;
- RightCon1B = 0;
- }
-
- void stop()
- {
- LeftCon1A = 0;
- LeftCon1B = 0;
-
- RightCon1A = 0;
- RightCon1B = 0;
- }
-