故事的由来的从给学弟学妹门辅导一次单片机综合实验课开始,由于笔者比较喜欢电子控制类的东西,自进入学校就迷上了,后来无意间加入了学校众创空间下的一个工作室成为一员,至此开启了嵌入式学习之路。。。。。。。
以下为了减少文章篇幅,主要展示51程序,STM32程序文件 详见文末说明。
不废话了,下面为了增加内容,摘点小学弟的实验报告(doge~)
我们小组使用光电传感器来采集路面信息。使用红外光电对管,其结构简明,实现方便,成本低廉,没有复杂的图像处理工作,因此反应灵敏,响应时间少。但也存在不足,它能获取的信息是不完全的,容易受很多扰动(如背景光源,高度等)的影响,抗干扰能力较差。但本次实验要求精度并不是太高,出于成本和设计复杂度方面考虑,我们决定采用红外对管来完成传感器模块。
下图为一光电循迹模块的基本电路原理图:
两节18650电池组成的7.4V锂电池组,因为锂电池性能稳定,还可以充电,循环使用,关键是动力强,就算经过PWM调制也能够稳定驱动小车循迹。(这里由于开环控制)
电机采用直流有刷电机,其转动力矩大,体积小,重量轻,装配简单,操作方便。速度的调节可以改变电压也可以调节PWM,搭配L298电机驱动模块组装方便。(其实就是TT减速电机)
检测到非黑色路面的红外接收头处理后送出的是低电平,而检测到黑色路线的检测头送出的是高电平,由此可根据这两个红外接收头的高低电平判断路线情况而调整小车前进方向。具体情况有如下几种:
打个头,也就值我给他们讲解的程序,课堂上临时写的,可能有些仓促不够完美。
Car.h文件:
#ifndef _CAR_H_
#define _CAR_H_
#include
typedef unsigned char u8;
typedef unsigned int u16;
/****** 四轮请去掉注释 **********/
//#define _4X_
/* L298N逻辑引脚定义 */
sbit IN1 = P1^2;
sbit IN2 = P1^3;
sbit IN3 = P1^4;
sbit IN4 = P1^5;
/* 循迹模块输入 */
sbit INL = P3^0;
sbit INR = P3^1;
//小车根据循迹模块自主控制
void Car_Scan(); //小车
#endif
Car.c文件:
#include "Car.h"
//小车根据循迹模块自主控制
void Car_Scan() //小车
{
if(INL==0&&INR==0) //直行
{
IN1=1; IN2=0; //左论正转
IN3=1; IN4=0; //右轮正转
}
#ifdef _4X_
//四轮车 两驱
if(INL==1&&INR==0) // 左边检测到,向左校正
{
IN1=0; IN2=1; //左轮反转
IN3=1; IN4=0; //右轮正转
}
if(INL==0&&INR==1) // 右边检测到,向右校正
{
IN1=1; IN2=0; //左论正转
IN3=0; IN4=1; //右轮反转
}
#else
// 三轮 两驱
if(INL==1&&INR==0) // 左边检测到,向左校正
{
IN1=1; IN2=1; //左轮停转
IN3=1; IN4=0; //右轮正转
}
if(INL==0&&INR==1) // 右边检测到,向右校正
{
IN1=1; IN2=0; //左论正转
IN3=1; IN4=1; //右轮停转
}
#endif
if(INL==1&&INR==1) // 两边检测到或小车离地,停车
{
IN1=1; IN2=1;
IN3=1; IN4=1;
}
}
pwm.h文件 这里利用定时器0产生
#ifndef _PWM_H
#define _PWM_H
#include
#include "Car.h"
extern u8 PWM;
extern u8 time;
sbit Pwm0 = P1^0; //PWM输出引脚
sbit Pwm1 = P1^1; //PWM输出引脚
void Pwm_Init();
#endif
pwm.c文件 ,大多数人拿到的是89C51,还有部分STC12的,尽管12已经带有硬件PWM功能了,但为了方便 这里默认老方式向STC89C51看起了,注意这个程序给12C51用需要将其设置为12T模式,这是为了使得工作步调相同,也就不同修改程序了。
#include "pwm.h"
#include "Car.h"
u8 PWM = 50; //pwm占空比为50%,可调占空比
u8 time = 0;
void Pwm_Init() // 100us中断,8位自动重载
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x02; //设置定时器模式 8位重载
TL0 = 0xA4; //设置定时初始值
TH0 = 0xA4; //设置定时重载值
TF0 = 0; //清除TF0标志
EA=1; //总中断打开
ET0=1; //定时器0中断使能
TR0=1; //定时器0开关打开
}
void Timer0_PwmGo() interrupt 1
{
time++;
if(time >= 100) time = 0; //PWM周期为100us*100=100Hz=0.01s=10ms
if(time < PWM)
{
Pwm0 = 1; //高电平
Pwm1 = 1;
}
if(time >= PWM)
{
Pwm0 = 0; //低电平
Pwm1 = 0;
}
}
主程序 main.c
#include "Car.h"
#include "pwm.h"
void main()
{
Pwm_Init(); //标值100,占空比50%
while(1)
{
Car_Scan(); //循迹扫描
}
}
Car.h 文件 主要定义一些IO和相关函数声明
#ifndef __CAR_H
#define __CAR_H
#include "sys.h"
#define RIN_L PAin(0) //PA0 左边 循迹模块
#define RIN_R PAin(1) //PA1 右边 循迹模块
//顺便定义一下 L298N 的四个输出 引脚
#define IN1 PAout(2) //PA2 左边 电机
#define IN2 PAout(3) //PA3
#define IN3 PAout(4) //PA4 右边 电机
#define IN4 PAout(5) //PA5
// 小车 函数声明
void Forward(void);
void Retreat(void);
void Turn_Left(void);
void Turn_Right(void);
void Stop(void);
void Car_Init(void); //IO初始化
void Car_Scan(void); //循迹小车 执行函数
#endif
Car.c文件:
#include "stm32f10x.h"
#include "key.h"
#include "sys.h"
#include "delay.h"
//按键初始化函数
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能端口A时钟
//初始化 WK_UP-->GPIOA.0-1 输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //KEY0-KEY1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA0,1
//初始化 WK_UP-->GPIOA.2-5 输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //PP-通用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz(最大)
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5); //全部初始化为高
}
// =========== 小车 循迹模块状态扫描 ================
void Car_Scan()
{
/*.....................*/
}
/* 小车 行动函数 */
由于循迹逻辑 和小车行动函数和51一模一样,此处不再展示-----------------------
其实这样就可以跑了,这里给大家放上串口控制的程序,这样就可与搭配蓝牙模块实现遥控小车功能!
UART.h文件
#define __USART_H
#include "stdio.h" //printf
#include "sys.h"
#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
extern u8 RxDated;
#define SendOK USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET //发送完毕中断标志
#define ReciveOK USART_GetFlagStatus(USART1,USART_IT_RXNE) //接收完毕中断标志
//如果想串口中断接收,请不要注释以下宏定义
void USART1_Init(u32 BaudRateS);
#endif
UART.c文件,这里主要是串口接收 数据处理,以及对接收到的苏数据分析
#include "sys.h"
#include "usart1.h"
#include "led.h"
#include "key.h" //包含 小车逻辑 引脚定义
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0)
; //循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
//使用microLib的方法
/*
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}
return ch;
}
int GetKey (void) {
while (!(USART1->SR & USART_FLAG_RXNE));
return ((int)(USART1->DR & 0x1FF));
}
*/
#if EN_USART1_RX //如果使能了接收
u8 USART_RX_BUF[USART_REC_LEN]; //定义一个长度为USART_REC_LEN的串口接收缓冲字符数组,将接收到的数据存入其中,
//长度为USART_REC_LEN个字节,具体长度依据实际数据,默认设为200.
/************************************************
* 串口1中断服务程序
* 注意,读取USARTx->SR能避免莫名其妙的错误
* u8 USART_RX_BUF[USART_REC_LEN]
* 接收状态
* bit15, 接收完成标志
* bit14, 接收到0x0d
* bit13~0,接收到的有效字节数目
*************************************************/
u16 USART_RX_STA=0; //接收状态标记
void USART1_Init(u32 BaudRateS){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体变量
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子(响应)优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = BaudRateS; //串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
//串口1中断服务程序,串口1有数据来了要干的事
void USART1_IRQHandler(void)
{
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
u8 RxDated, RxDate; //定义一个8位的变量来存放数据
//此处定义了一种接收协议 (接收到的数据必须是0x0d 0x0a结尾,不是则重新接收),保证数据完整性
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
RxDated =USART_ReceiveData(USART1); //读取接收到的数据赋给 RxDated
if((USART_RX_STA&0x8000)==0) //接收未完成
{
if(USART_RX_STA&0x4000) //接收到了0x0d
{
if(RxDated!=0x0a)USART_RX_STA=0; //接收错误,重新开始
else USART_RX_STA|=0x8000; //一个字节接收成功 0x8000 即换行\r\n,可作为数据收发结束的标志
}
else //还没收到0X0D,即还未完全(完整)接收数据
{
if(RxDated==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=RxDated; //将接收到的数据存入USART_RX_BUF数组
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))
USART_RX_STA=0; //接收数据错误,重新开始接收
}
}
}
}
/******* 用户串口任务执行段 **********/
if(USART_GetFlagStatus(USART1,USART_IT_RXNE)) //若已完成数据接收
{
RxDate = *USART_RX_BUF; //将字符数组中的数据读取出来
switch(RxDate) //switch 数据处理函数
{
case 'U': Forward();
break;
case 'D': Retreat();
break;
case 'L': Turn_Left();
break;
case 'R': Turn_Right();
break;
case 'S': Stop();
break;
/************************/
case '0': LED1=1;
printf("灯已关闭\r\n");
break;
case '1': LED1=0;
printf("灯已打开\r\n");
break;
}
}
//执行任务结束
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
主程序main.c
/************************************************
* 简单遥控+循迹小车程序
* 接线:两路循迹模块输入引脚 PA1-PA2
* L298N电机控制引脚 PA2-PA5
* TIM3用作PWM输出 PA6-PA7
* 平台:STM32F103C8T6核心板
* @作者: Guard_Byte
************************************************/
#include "led.h"
#include "key.h"
#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "timer.h"
int main(void)
{
NVIC_Group2; //设置NVIC中断分组2;2位抢占优先级,2位响应优先级
USART1_Init(9600); //串口1初始化为9600
PWM_TIM3_Inits(99,36-1); //PWM频率 20kHz 占空比 0到100 可调
delay_init(); //延时函数初始化
LED_Init(); //LED端口初始化
KEY_Init(); //初始化与按键连接的硬件接口,包括小车I/O定义
LED1=1;
while(1)
{
Car_Scan();
}
}
本来写STM32的,没想到51程序贴太多了,直接这里就不多废话了,文件自由下载
百度网盘链接:https://pan.baidu.com/s/1PXEE60j1ru0KjL39TY__kw?pwd=bxmw
提取码:bxmw
或个人代码仓库 https://gitee.com/Guard_Byte/stm32-f103-learning-record/tree/master
项目文件夹 目录下