目录
硬件准备:
一个C51单片机
两个舵机(SG90)
一个USB转TTL
软件环境:
Keil5,STC-ISP,Proteus 8 Professional,Configure Virtual Serial Port Driver
作品现象:
实物:
通过电脑串口输入转动角度来分别控制两个舵机的转动角度,输入一个数据控制第一个舵机,输入第二个数据控制第二个舵机角度,循环往复
仿真:
通过虚拟串口实现在仿真上的串口发送接收
硬件连线:
引脚说明
C51 | Servo1 |
P2^3 | 信号线(黄色) |
C51 | servo2 |
P2^4 | 信号线(黄色) |
棕色是GND接C51的GND,红色是VCC接C51的VCC(SG90需要5V电压,驱动多个舵机时,C51的5V引脚不够,①要么单独供电,②要么使用面包板之类的将5V引脚增多,③要么使用舵机驱动板,如PCA9685舵机驱动模块,否则舵机要么带不动,要么会发生抽搐)
C51 | LCD1602 |
RW | P2^5 |
RS | P2^6 |
E | P2^7 |
GND | VSS、V0 |
P1^0 | D0 |
P1^1 | D1 |
P1^2 | D2 |
P1^3 | D3 |
P1^4 | D4 |
P1^5 | D5 |
P1^6 | D6 |
P1^7 | D7 |
VCC | A |
GND | K |
VCC | VDD |
C51 | USB转TTL |
GND | GND |
P3^0 | TX |
P3^1 | RX |
VCC | VCC |
串口通信中是(TX连RX;RX连TX)
代码实现
main.c
#include <reg51.h>
#include "LCD1602.h"
// 定义舵机控制引脚
sbit servo1 = P2^3;
sbit servo2 = P2^4;
// 定义全局变量
unsigned char angle1 = 0; // 舵机1的角度
unsigned char angle2 = 0; // 舵机2的角度
unsigned char count = 0; // 定时器计数
// 函数声明
void UART_Init();
void Timer0_Init();
void delay_ms(unsigned int ms);
void set_angle(unsigned char *angle, unsigned char new_angle);
// 主函数
void main() {
UART_Init(); // 初始化串口
Timer0_Init();// 初始化定时器0
EA = 1; // 全局中断使能
LCD_Init(); // 初始化 LCD1602
while(1) {
// 显示舵机1的角度
LCD_ShowString(1, 1, "Servo1:");
LCD_ShowNum(1, 8, angle1, 3);
// 显示舵机2的角度
LCD_ShowString(2, 1, "Servo2:");
LCD_ShowNum(2, 8, angle2, 3);
delay_ms(100); // 适当延时,避免刷新过快
}
}
// 串口初始化函数
void UART_Init() {
TMOD |= 0x20; // 设置定时器1为模式2
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
SCON = 0x50; // 8位数据,可变波特率
TR1 = 1; // 启动定时器1
ES = 1; // 使能串口中断
}
// 定时器0初始化函数
void Timer0_Init() {
TMOD |= 0x01; // 设置定时器0为模式1
TH0 = 0xFF; // 定时器初值,定时约100us
TL0 = 0x9C;
ET0 = 1; // 使能定时器0中断
TR0 = 1; // 启动定时器0
}
// 串口中断服务函数
void UART_ISR() interrupt 4 {
static unsigned char servo_select = 0; // 新增变量,用于选择控制的舵机
if (RI) {
RI = 0; // 清除接收中断标志
if (SBUF < 181) { // 确保角度在0-180度范围内
if (servo_select == 0) {
angle1 = SBUF;
servo_select = 1; // 下次控制舵机2
} else {
angle2 = SBUF;
servo_select = 0; // 下次控制舵机1
}
SBUF = SBUF; // 回显接收到的数据
while (!TI); // 等待发送完成
TI = 0; // 清除发送中断标志
}
}
}
// 定时器0中断服务函数
void Timer0_ISR() interrupt 1 {
TH0 = 0xFF; // 重新加载定时器初值
TL0 = 0x9C;
count++;
if (count < 200) { // 20ms周期
if (count < (angle1 / 9 + 5)) { // 计算占空比
servo1 = 1;
} else {
servo1 = 0;
}
if (count < (angle2 / 9 + 5)) { // 计算占空比
servo2 = 1;
} else {
servo2 = 0;
}
} else {
count = 0;
}
}
// 延时函数
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++) {
for (j = 0; j < 123; j++);
}
}
// 设置舵机角度函数
void set_angle(unsigned char *angle, unsigned char new_angle) {
if (new_angle < 181) {
*angle = new_angle;
}
}
LCD1602.c代码如下
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P1
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms(简介)
* @param 无(参数)
* @retval 无(返回值)
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
LCD1602.h代码如下
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
实物演示如下:(在STC-ISP串口助手中发送转动角度即可,注意C51接收的是16进制,即发送的角度会被转化为16进制的角度)
串口控制舵机转动角度
仿真如下:
在Protues中虚拟串口的位置如下:
演示视频如下:
虚拟串口仿真
虚拟串口要想实现,需要先配置虚拟串口,利用Configure Virtual Serial Port Driver软件(确保自己没有安装过该环境,否则再安装一次会发生奇奇怪怪的问题)