草庐IT

【基于51】红外寻迹智能小车 - 代码篇

小白还在写代码 2023-04-20 原文

文章目录


前言

关于硬件部分可以看我上次写的帖子https://blog.csdn.net/ZER00000001/article/details/124378788
#新人报道我是普通本科大一新生,因为兴趣爱好加入学校机器人协会,计划本学期挑战电赛|ू・ω・` )没想到疫情突发,只能匆匆用了一个星期入门51,做阶段性小项目,基于51智能红外寻迹小车,虽然论坛教程很多,但还是发此贴记录分享撸车的时光,也顺便做个引子给刚入门的同好们…


一、准备工作

1.安装keil 5
2.有一定的c语言基础
3.明白各模块工作原理,才能写逻辑

二、使用步骤

1.模块化编程

【视频教程:b站江科大自化协】我跟着这位老师入门的51,他讲的很好,其中教会了我模块化编程(可以有效降低代码耦合性,并且写下来思路非常清晰!!!),所以在接下的说明中我会按照这种方式来讲解代码( ̄▽ ̄)~*
(代码不成熟,仅供参考)

2.电机模块

这是电机模块,也就是控制轮子怎么转的。(如果你的电机反向,就把1和0交换位置)

#include <REGX52.H>

sbit IN1 = P1^0;//左轮
sbit IN2 = P1^1;//左轮
sbit IN3 = P1^2;//右轮
sbit IN4 = P1^3;//右轮

void Left_Motor_Forward()//左轮前进
{
	IN1 = 1;
	IN2 = 0;
}

void Right_Motor_Forward()//右轮前进
{
	IN3 = 1;
	IN4 = 0;
}
//
void Left_Motor_Back()//左轮后退
{
	IN1 = 0;
	IN2 = 1;
}

void Right_Motor_Back()//右轮后退
{
	IN3 = 0;
	IN4 = 1;
}
//
void Left_Motor_Stop()//左轮停
{
	IN1 = 0;
	IN2 = 0;
}

void Right_Motor_Stop()//右轮停
{
	IN3 = 0;
	IN4 = 0;
}

这是函数封装

#ifndef __MOTORSET_H__
#define __MOTORSET_H__


void Left_Motor_Forward();
void Right_Motor_Forward();
void Left_Motor_Back();
void Right_Motor_Back();
void Left_Motor_Stop();
void Right_Motor_Stop();

#endif

3.小车动作模块

利用封装好的电机模块对小车整体控制

#include <REGX52.H>
#include "MotorSet.h"

void Car_Go()
{Left_Motor_Forward();Right_Motor_Forward();}

void Car_Left()
{Left_Motor_Stop();Right_Motor_Forward();}

void Car_Right()
{Right_Motor_Stop();Left_Motor_Forward();}

void Car_Stop()
{Left_Motor_Stop();Right_Motor_Stop();}

void Car_Back()
{Left_Motor_Back();Right_Motor_Back();}

封装

#ifndef __MOTORSET_H__
#define __MOTORSET_H__


void Left_Motor_Forward();
void Right_Motor_Forward();
void Left_Motor_Back();
void Right_Motor_Back();
void Left_Motor_Stop();
void Right_Motor_Stop();

#endif

4**.PWM**和定时器、中断系统

关于PWM


CSDN上也有足够详细的讲解:
pwm的频率:
是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
也就是说一秒钟PWM有多少个周期
单位: Hz
表示方式: 50Hz 100Hz
pwm的周期:
T=1/f
周期=1/频率
50Hz = 20ms 一个周期
如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
占空比:
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%

以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平
假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号
我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压
比方说 占空比为50% 那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比 得到的电压就是3.75V

图 14.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候, IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1), 当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。

关于定时器的配置,这里我们使用的是计时器0模式1
(图片为定时器和中断系统的原理图)

定时器0的初始化

#include <REGX52.H>

sbit ENA_1 = P3^0;
sbit ENA_2 = P3^1;
sbit ENB_1 = P3^2;
sbit ENB_2 = P3^3;

/**
  * @brief  定时器0初始化,100us@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)		//100微秒@11.0592MHz
{

	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = (65536-100)%256;		//设置定时初始值
	TH0 = (65536-100)/256;		//设置定时初始值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

利用中断函数实现PWM


unsigned int pwml,pwmr,t;//左右占空比,比较值
void Timer0_Routine() interrupt 1
{
	TL0 = (65536-100)%256;		//设置定时初始值
	TH0 = (65536-100)/256;		//设置定时初始值
	t++;
	if(t<pwml)
	{
		ENA_1 = 1;
		ENA_2 = 1;
	}
	else
	{
		ENA_1 = 0;
		ENA_2 = 0;
	}
//左pwm
	if(t<pwmr)
	{
		ENB_1 = 1;
		ENB_2 = 1;
	}
	else
	{
		ENB_1 = 0;
		ENB_2 = 0;
	}
//右pwm
	if(t>=100){t = 0;}
}

定时器模块的封装

#ifndef __TIMER0_H__
#define __TIMER0_H__


extern pwml;
extern pwmr;
extern t; 
void Timer0_Init(void);

#endif

整个撸车过程中耗时最长的就属调车的参数了,需要根据现场情况,一点点找到合适的占空比,来实现两轮转速的相对平衡(高级多的可以用一些带编码器的电机或物理仿真来找到合适的参数);

5.寻迹逻辑

我们使用的是红外避障模块(和寻迹原理一样,详细见我另一篇文章【基于51】红外寻迹智能小车 - 硬件篇)
但模块接收回红外信号时,OUT端返回低电平;但在黑线上时,黑线吸收红外,模块没有信号返回,OUT端返回高电平。
根据以上原理,我们可以写出寻迹逻辑

#include <REGX52.H>
#include "CarGo.h"
#include "Timer0.h"
#include "Delay.h"

sbit OUT1 = P2^5;//左大调
sbit OUT2 = P2^4;//左微调
sbit OUT4 = P2^2;//右大调
sbit OUT3 = P2^1;//右微调

sbit IN1 = P1^0;//左轮
sbit IN2 = P1^1;//左轮
sbit IN3 = P1^2;//右轮
sbit IN4 = P1^3;//右轮

extern pwml;
extern pwmr;

void Chack()
{
	if(OUT1 == 1 && OUT2 == 1 && OUT3 == 1 && OUT4 == 1)//全黑线(或空中)停车
	{
		pwml = 0;
		pwmr = 0;
		Car_Stop();
	}

	if(OUT1 == 0 && OUT2 == 0 && OUT3 == 1 && OUT4 == 0)//偏左,右移
	{
		pwml = 20;
		pwmr = 16;
		Car_Go();
	}

	if(OUT1 == 0 && OUT2 == 1 && OUT3 == 0 && OUT4 == 0)//偏右,左移
	{
	    pwml = 16;
		pwmr = 20;
		Car_Go();
	}
///
	if(OUT1 == 1 && OUT2 == 1 && OUT3 == 0 && OUT4 == 0)//左大转
	{
//		Car_Stop();
//		Delay(20);
//		pwml = 5;
//		pwmr = 30;
//		Delay(20);
//		Car_Go();
		
		while(1)
		{
				IN1 = 0;
				IN2 = 1;
				if(OUT3 == 1) {
				IN1 = 1;
				IN2 = 0;
				break;}
				pwml = 18;
				pwmr = 18;
				Car_Go();
				
		}
	}
//	if(OUT1 == 0 && OUT2 == 0 && OUT3 == 0 && OUT4 == 1)//右大转
//	{
		Car_Stop();
		Delay(20);
		pwml = 5;
		pwmr = 30;
		Delay(20);
		Car_Go();
//		
//		while(1)
//		{
//			IN3 = 0;
//				IN4 = 1;
//				if(OUT2 == 1) {
//				IN3 = 1;
//				IN4 = 0;
//				break;}
//				pwml = 25;
//				pwmr = 25;
//				Car_Go();
//				
//		}
//	}
///
	if(OUT1 == 1 && OUT2 == 0 && OUT3 == 0 && OUT4 == 1)
	{
		pwml = 16;
		pwmr = 16;
		Car_Go();
	}
}

在上篇我说过,我们使用的是4个红外模块平行放置来检测黑线,在调整好红外灵敏度和模块间距离(当中间两个正好能检测黑线时),靠外侧的模块用来判断转弯(这里我们考虑了用while实现的直角转弯);

函数封装

#ifndef __CHACK_H__
#define __CHACK_H__
#inlcude "MotorSet.h"
void Chack();
#endif

最后把函数全部调用进main函数就ok了ヾ(✿゚▽゚)ノ

总结

这次的小车虽然只是个阶段性小项目,但是收获颇多!下次准备挑战21电赛f组试题。希望大家都能享受亲自把小车跑起来时的快乐!

有关【基于51】红外寻迹智能小车 - 代码篇的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  4. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  8. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  9. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  10. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

随机推荐