日型管控制
展示如图。生成各种随机数,并有一个按钮来暂停以实现随机取数的功能。
const int latchPin = 12;//Pin connected to ST_CP of 74HC595
const int clockPin = 8;//Pin connected to SH_CP of 74HC595
const int dataPin = 11; //Pin connected to DS of 74HC595
const int ledPin = 13; //ledPin attach to
const int keyIn = 2; //keyIn attach to
int num = 0;
//display 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F
int datArray[16] = {
252, 96, 218, 242, 102, 182, 190, 224, 254, 246, 238, 62, 156, 122, 158, 142};
long randNumber;
void setup()
{
pinMode(latchPin,OUTPUT); //set the latchPin as an output
pinMode(clockPin,OUTPUT); //set the clockPin as an output
pinMode(dataPin,OUTPUT); //set the dataPin as an output
pinMode(ledPin,OUTPUT); //set the ledPin as an output
pinMode(keyIn,INPUT); //set the keyIn as an input
Serial.begin(9600); // start serial port at 9600 bps:
//initialize the random number generator with a fairly random input A0
randomSeed(analogRead(0));
}
void loop()
{
int stat = digitalRead(keyIn); //store value read from keyIn
if(stat == HIGH) // check if the pushbutton is pressed
{
num ++;
if(num > 1)
{
num = 0;
}
}
Serial.println(num); // print the num on serial monitor
if(num == 1) //when pushbutton is pressed
{
randNumber = random(1,7); //Generate a random number in 1-7
showNum(randNumber); //show the randNumber on 7-segment
delay(1000); //wait for 1 second
while(!digitalRead(keyIn)); //When not press button,program stop here
int stat = digitalRead(keyIn);
if(stat == HIGH) // check if the pushbutton is pressed
{
num ++;
digitalWrite(ledPin,HIGH); //turn on the led
delay(100);
digitalWrite(ledPin,LOW); //turn off the led
delay(100);
if(num >= 1)
{
num = 0;
}
}
}
//show random numbers at 100 microseconds intervals
// If the button has not been pressed
randNumber = random(1,7);
showNum(randNumber);
delay(100);
}
//the function to drive 7-segment show numbers
void showNum(int num)
{
digitalWrite(latchPin,LOW); //ground latchPin and hold low for transmitting
shiftOut(dataPin,clockPin,MSBFIRST,datArray[num]);
//return the latch pin high to signal chip that it
//no longer needs to listen for information
digitalWrite(latchPin,HIGH); //pull the latchPin to save the data
}
平衡小车
平衡小车利用感应器(内置加速度、重力和角度的感应功能)来控制马达的转动,以此保持平衡。马达的加速度什么的还是比较难设置的。
反复横跳 .gif
#include <PinChangeInt.h>
#include <MsTimer2.h>
//利用编码器计数实现速度PID控制
#include <BalanceCar.h>
#include <KalmanFilter.h>
//I2Cdev、MPU6050类库需要事先安装在Arduino 类库文件夹下
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
MPU6050 mpu; //实例化一个 MPU6050 对象,对象名称为 mpu
BalanceCar balancecar;
KalmanFilter kalmanfilter;
int16_t ax, ay, az, gx, gy, gz;
//TB6612FNG驱动模块控制信号
#define IN1M 7
#define IN2M 6
#define IN3M 13
#define IN4M 12
#define PWMA 9
#define PWMB 10
#define STBY 8
//编码器计数信号
#define PinA_left 2 //中断0
#define PinA_right 4 //中断1
//声明自定义变量
int time;
byte inByte; //串口接收字节
int num;
double Setpoint;//角度DIP设定点,输入,输出
double Setpoints, Outputs = 0;//速度DIP设定点,输入,输出
double kp = 40, ki = 0.0, kd = 0.6;//需要你修改的参数,角度PD控制
double kp_speed =5.20, ki_speed = 0.25, kd_speed = 0.0;// 需要你修改的参数,速度PI控制
double kp_turn = 23, ki_turn = 0, kd_turn = 0.3;//旋转PD设定
const double PID_Original[6] = {40, 0.0, 0.6, 5.20, 0.25, 0.0}; //恢复默认PID参数
//转向PID参数
double setp0 = 0, dpwm = 0, dl = 0; //角度平衡点,PWM差,死区,PWM1,PWM2
float value;
/********************angle data*********************/
float Q;
float Angle_ax; //由加速度计算的倾斜角度
float Angle_ay;
float K1 = 0.05; // 对加速度计取值的权重
float angle0 = 0.00; //机械平衡角
int slong;
/***************Kalman_Filter*********************/
float Q_angle = 0.001, Q_gyro = 0.005; //角度数据置信度,角速度数据置信度
float R_angle = 0.5 , C_0 = 1;
float timeChange = 5; //滤波法采样时间间隔毫秒
float dt = timeChange * 0.001; //注意:dt的取值为滤波器采样时间
/***************Kalman_Filter*********************/
/******************** speed count ************/
volatile long count_right = 0;//使用volatile lon类型是为了外部中断脉冲计数值在其他函数中使用时,确保数值有效
volatile long count_left = 0;//使用volatile lon类型是为了外部中断脉冲计数值在其他函数中使用时,确保数值有效
int speedcc = 0;
/********************脉冲计算********************/
int lz = 0;
int rz = 0;
int rpluse = 0;
int lpluse = 0;
int sumam;
/********************转向、旋转参数********************/
int turncount = 0; //转向介入时间计算
float turnoutput = 0;
/********************蓝牙控制量********************/
#define run_car '1'//按键前
#define back_car '2'//按键后
#define left_car '3'//按键左
#define right_car '4'//按键右
#define stop_car '0'//按键停
/********************小车运行状态枚举********************/
enum {
enSTOP = 0,
enRUN,
enBACK,
enLEFT,
enRIGHT,
enTLEFT,
enTRIGHT
} enCarState;
int incomingByte = 0; // 接收到的 data byte
String inputString = ""; // 用来储存接收到的内容
boolean newLineReceived = false; // 前一次数据结束标志
boolean startBit = false; //协议开始标志
int g_carstate = enSTOP; // 1前2后3左4右0停止
String returntemp = ""; //存储返回值
boolean g_autoup = false;
int g_uptimes = 5000;
int front = 0;//前进变量
int back = 0;//后退变量
int turnl = 0;//左转标志
int turnr = 0;//右转标志
int spinl = 0;//左旋转标志
int spinr = 0;//右旋转标志
int bluetoothvalue;//蓝牙控制量
/********************超声波速度********************/
int chaoshengbo = 0;
int tingzhi = 0;
int jishi = 0;
/********************脉冲计算********************/
void countpluse()
{
lz = count_left;
rz = count_right;
count_left = 0;
count_right = 0;
lpluse = lz;
rpluse = rz;
if ((balancecar.pwm1 < 0) && (balancecar.pwm2 < 0))//小车运动方向判断 后退时(PWM即电机电压为负) 脉冲数为负数
{
rpluse = -rpluse;
lpluse = -lpluse;
}
else if ((balancecar.pwm1 > 0) && (balancecar.pwm2 > 0))//小车运动方向判断 前进时(PWM即电机电压为正) 脉冲数为负数
{
rpluse = rpluse;
lpluse = lpluse;
}
else if ((balancecar.pwm1 < 0) && (balancecar.pwm2 > 0))//小车运动方向判断 前进时(PWM即电机电压为正) 脉冲数为负数
{
rpluse = rpluse;
lpluse = -lpluse;
}
else if ((balancecar.pwm1 > 0) && (balancecar.pwm2 < 0))//小车运动方向判断 左旋转 右脉冲数为负数 左脉冲数为正数
{
rpluse = -rpluse;
lpluse = lpluse;
}
//提起判断
balancecar.stopr += rpluse;
balancecar.stopl += lpluse;
//每5ms进入中断时,脉冲数叠加
balancecar.pulseright += rpluse;
balancecar.pulseleft += lpluse;
sumam = balancecar.pulseright + balancecar.pulseleft;
}
/********************角度PD********************/
void angleout()
{
balancecar.angleoutput = kp * (kalmanfilter.angle + angle0) + kd * kalmanfilter.Gyro_x;//PD 角度环控制
}
/********************中断定时5ms定时中断********************/
void inter()
{
sei();//开中断
countpluse();//脉冲叠加子函数
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);//IIC获取MPU6050六轴数据 ax ay az gx gy gz
kalmanfilter.Angletest(ax, ay, az, gx, gy, gz, dt, Q_angle, Q_gyro, R_angle, C_0, K1);//获取angle 角度和卡曼滤波
angleout();//角度环 PD控制
speedcc++;
if (speedcc >= 10)//50ms进入速度环控制
{
Outputs = balancecar.speedpiout(kp_speed, ki_speed, kd_speed, front, back, setp0);
speedcc = 0;
}
turncount++;
if (turncount > 4)//20ms进入旋转控制
{
turnoutput = balancecar.turnspin(turnl, turnr, spinl, spinr, kp_turn, kd_turn, kalmanfilter.Gyro_z);//旋转子函数
turncount = 0;
}
balancecar.posture++;
balancecar.pwma(Outputs, turnoutput, kalmanfilter.angle, kalmanfilter.angle6, turnl, turnr, spinl,\
spinr, front, back, kalmanfilter.accelz, IN1M, IN2M, IN3M, IN4M, PWMA, PWMB);//小车总PWM输出
}
/********************中断定时 5ms定时中断********************/
void SendAutoUp()
{
g_uptimes --;
if ((g_autoup == true) && (g_uptimes == 0))
{
//自动上报
String CSB, VT;
char temp[10]={0};
float fgx;
float fay;
float leftspeed;
float rightspeed;
fgx = gx;//传给局部变量
fay = ay;//传给局部变量
leftspeed = balancecar.pwm1;
rightspeed = balancecar.pwm2;
double Gx = (double)((fgx - 128.1f) / 131.0f);//角度转换
double Ay = ((double)fay / 16384.0f) * 9.8f;
if(leftspeed > 255 || leftspeed < -255)
return;
if(rightspeed > 255 || rightspeed < -255)
return;
if((Ay < -20) || (Ay > 20))
return;
if((Gx < -3000) || (Gx > 3000))
return;
returntemp = "";
memset(temp, 0x00, sizeof(temp));
//sprintf(temp, "%3.1f", leftspeed);
dtostrf(leftspeed, 3, 1, temp); // 相當於 %3.2f
String LV = temp;
memset(temp, 0x00, sizeof(temp));
//sprintf(temp, "%3.1f", rightspeed);
dtostrf(rightspeed, 3, 1, temp); // 相當於 %3.1f
String RV = temp;
memset(temp, 0x00, sizeof(temp));
//sprintf(temp, "%2.2f", Ay);
dtostrf(Ay, 2, 2, temp); // 相當於 %2.2f
String AC = temp;
memset(temp, 0x00, sizeof(temp));
//sprintf(temp, "%4.2f", Gx);
dtostrf(Gx, 4, 2, temp); // 相當於 %4.2f
String GY = temp;
CSB = "0.00";
VT = "0.00";
//AC =
returntemp = "$LV" + LV + ",RV" + RV + ",AC" + AC + ",GY" + GY + ",CSB" + CSB + ",VT" + VT + "#";
Serial.print(returntemp); //返回协议数据包
}
if (g_uptimes == 0)
g_uptimes = 5000;
}
/********************初始化设置********************/
void setup() {
// TB6612FNGN驱动模块控制信号初始化
pinMode(IN1M, OUTPUT);//控制电机1的方向,01为正转,10为反转
pinMode(IN2M, OUTPUT);
pinMode(IN3M, OUTPUT);//控制电机2的方向,01为正转,10为反转
pinMode(IN4M, OUTPUT);
pinMode(PWMA, OUTPUT);//左电机PWM
pinMode(PWMB, OUTPUT);//右电机PWM
pinMode(STBY, OUTPUT);//enable TB6612FNG
//初始化电机驱动模块
digitalWrite(IN1M, 0);
digitalWrite(IN2M, 1);
digitalWrite(IN3M, 1);
digitalWrite(IN4M, 0);
digitalWrite(STBY, 1);
analogWrite(PWMA, 0);
analogWrite(PWMB, 0);
//测速码盘输入
pinMode(PinA_left, INPUT);
pinMode(PinA_right, INPUT);
// 加入I2C总线
Wire.begin();//加入 I2C 总线序列
Serial.begin(9600);//开启串口,设置波特率为 115200
delay(1500);
mpu.initialize();//初始化MPU6050
delay(2);
balancecar.pwm1 = 0;
balancecar.pwm2 = 0;
//5ms定时中断设置,使用timer2.注意:使用timer2会对pin3 pin11的PWM输出有影响,
//因为PWM使用的是定时器控制占空比,所以在使用timer的时候要注意查看对应timer的pin口。
MsTimer2::set(5, inter);
MsTimer2::start();
}
/********************复位PID参数********************/
void ResetPID()
{
kp = PID_Original[0];
ki = PID_Original[1];
kd = PID_Original[2];//需要你修改的参数
kp_speed = PID_Original[3];
ki_speed = PID_Original[4];
kd_speed = PID_Original[5];//需要你修改的参数
}
/********************复位小车状态********************/
void ResetCarState()
{
turnl = 0;
turnr = 0;
front = 0;
back = 0;
spinl = 0;
spinr = 0;
turnoutput = 0;
}
/********************主循环********************/
void loop()
{
String returnstr = "$0,0,0,0,0,0,0,0,0,0,0,0cm,8.2V#";//默认发送
//主函数中循环检测及叠加脉冲 测定小车车速 使用电平改变既进入脉冲叠加 增加电机的脉冲数,保证小车的精确度。
attachInterrupt(0, Code_left, CHANGE);
attachPinChangeInterrupt(PinA_right, Code_right, CHANGE);
if (newLineReceived)
{
switch (inputString[1])
{
case run_car: g_carstate = enRUN; break;
case back_car: g_carstate = enBACK; break;
case left_car: g_carstate = enLEFT; break;
case right_car: g_carstate = enRIGHT; break;
case stop_car: g_carstate = enSTOP; break;
default: g_carstate = enSTOP; break;
}
//判断协议是否有丢包
if (inputString[3] == '1' && inputString.length() == 21)//左摇
{
g_carstate = enTLEFT;
//Serial.print(returnstr);
}
else if (inputString[3] == '2' && inputString.length() == 21)//右摇
{
g_carstate = enTRIGHT;
// Serial.print(returnstr);
}
if (inputString[5] == '1') //查询PID
{
char charkp[7], charkd[7], charkpspeed[7], charkispeed[7];
dtostrf(kp, 3, 2, charkp);//相当于 %3.2f
dtostrf(kd, 3, 2, charkd);//相当于%3.2f
dtostrf(kp_speed, 3, 2, charkpspeed);//相当于%3.2f
dtostrf(ki_speed, 3, 2, charkispeed);//相当于%3.2f
String strkp = charkp; String strkd = charkd;
String strkpspeed = charkpspeed; String strkispeed = charkispeed;
returntemp = "$0,0,0,0,0,0,AP" + strkp + ",AD" + strkd + ",VP" + strkpspeed + ",VI" + strkispeed + "#";
Serial.print(returntemp); //返回协议数据包
}
else if (inputString[5] == '2')//恢复PID
{
ResetPID();
Serial.print("$OK#");//返回协议数据包
}
if (inputString[7] == '1')//自动上报
{
g_autoup = true;
Serial.print("$OK#");//返回协议数据包
}
else if (inputString[7] == '2')//停止自动上报
{
g_autoup = false;
Serial.print("$OK#"); //返回协议数据包
}
if (inputString[9] == '1') //角度环更新 $0,0,0,0,1,1,AP23.54,AD85.45,VP10.78,VI0.26#
{
int i = inputString.indexOf("AP");
int ii = inputString.indexOf(",", i);
if(ii > i)
{
String m_skp = inputString.substring(i + 2, ii);
m_skp.replace(".", "");
int m_kp = m_skp.toInt();
kp = (double)( (double)m_kp / 100.0f);
}
i = inputString.indexOf("AD");
ii = inputString.indexOf(",", i);
if(ii > i)
{
//ki = inputString.substring(i+2, ii);
String m_skd = inputString.substring(i + 2, ii);
m_skd.replace(".", "");
int m_kd = m_skd.toInt();
kd = (double)( (double)m_kd / 100.0f);
}
Serial.print("$OK#");//返回协议数据包
}
if (inputString[11] == '1') //速度环更新
{
int i = inputString.indexOf("VP");
int ii = inputString.indexOf(",", i);
if(ii > i)
{
String m_svp = inputString.substring(i + 2, ii);
m_svp.replace(".", "");
int m_vp = m_svp.toInt();
kp_speed = (double)( (double)m_vp / 100.0f);
}
i = inputString.indexOf("VI");
ii = inputString.indexOf("#", i);
if(ii > i)
{
String m_svi = inputString.substring(i + 2, ii);
m_svi.replace(".", "");
int m_vi = m_svi.toInt();
ki_speed = (double)( (double)m_vi / 100.0f);
Serial.print("$OK#"); //返回协议数据包
}
}
//恢复默认
inputString = ""; // clear the string
newLineReceived = false;
}
a: switch (g_carstate)
{
case enSTOP:turnl = 0;turnr = 0;front = 0;back = 0;spinl = 0;spinr = 0;turnoutput = 0;break;
case enRUN:ResetCarState();front = 250;break;
case enLEFT:turnl = 1;break;
case enRIGHT:turnr = 1;break;
case enBACK:ResetCarState();back = -250;break;
case enTLEFT:spinl = 1;break;
case enTRIGHT:spinr = 1;break;
default:front = 0;back = 0;turnl = 0;turnr = 0;spinl = 0;spinr = 0;turnoutput = 0;break;
}
//增加自动上报
SendAutoUp();
}
/********************脉冲中断服务函数********************/
/********************左测速码盘计数********************/
void Code_left()
{
count_left ++;
}
/********************右测速码盘计数********************/
void Code_right()
{
count_right ++;
}
//serialEvent()是IDE1.0及以后版本新增的功能,不清楚为什么大部份人不愿意用,这个可是相当于中断功能一样的啊!
int num1 = 0;
void serialEvent()
{
while (Serial.available())
{
incomingByte = Serial.read();//一个字节一个字节地读,下一句是读到的放入字符串数组中组成一个完成的数据包
if (incomingByte == '$')
{
num1 = 0;
startBit = true;
}
if (startBit == true)
{
num1++;
inputString += (char) incomingByte;// 全双工串口可以不用在下面加延时,半双工则要加的//
}
if (startBit == true && incomingByte == '#')
{
newLineReceived = true;
startBit = false;
}
if(num1 >= 80)
{
num1 = 0;
startBit = false;
newLineReceived = false;
inputString = "";
}
}
}
/********************备份********************/
#if 0
char chartemp[7];
dtostrf(ax, 3, 2, chartemp);//相当于%3.2f
String strax = chartemp;
strax = "\nax:" + strax;
memset(chartemp, 0x00, 7);
dtostrf(ay, 3, 2, chartemp);//相当于%3.2f
String stray = chartemp;
stray = "\nay:" + stray;
memset(chartemp, 0x00, 7);
dtostrf(az, 3, 2, chartemp);//相当于%3.2f
String straz = chartemp;
straz = "\naz:" + straz;
memset(chartemp, 0x00, 7);
dtostrf(gx, 3, 2, chartemp);//相当于%3.2f
String strgx = chartemp;
strgx = "\ngx:" + strgx;
memset(chartemp, 0x00, 7);
dtostrf(gy, 3, 2, chartemp);//相当于%3.2f
String strgy = chartemp;
strgy = "\ngy:" + strgy;
memset(chartemp, 0x00, 7);
dtostrf(gz, 3, 2, chartemp);//相当于%3.2f
String strgz = chartemp;
strgz = "\ngz:" + strgz;
Serial.print(strax + stray + straz + strgx + strgy + strgz);//返回协议数据包
#endif