双足人型舞蹈机器人--中国机器人大赛二等奖

项目链接

https://github.com/chengjing11/Dance-Robot.git

项目说明

基于STC12C5A60S2 + CDS5516 + nRF24L01 + LCD1602 + openmv等设计的舞蹈机器人。

  • 整个项目包含一个主控台,三个舞蹈机器人;其中,一个机器人是主机器人,其余两个机器人为从机器人。

  • 三个机器人开机之后,为了实现舞蹈同步与机器人状态实时查看,在主控台使用按键开启/结束任务,并通过无线通信发送指令给主机器人;LCD2406液晶显示屏提示字幕可实时查看交互过程。

  • 文件中有小青、小粉、小蓝,小青为主机器人。

  • 比赛开始控制台通过无线发送指令,可保证三个机器人同时跳舞。跳舞结束后,主机器人等待进行人机交互,主要包含语音交互和视觉交互。

  • 歌曲使用《强军战歌》

各模块通信方式

串口1:相当于“print”的功能,用来打印字符、传递消息。

串口2:语音交互

串口3数字舵机控制

串口4视觉交互(openmv)

SPI通信:NRF24L01无线通信使用SPI通信方式

核心代码

主程序–控制台

# include "narfl24l01.h"
# include "LCD2406.h"
sbit key = P0^4;
unsigned char a;
void three(unsigned int date)
{
	TF_senddat(date);
	TF_senddat(date);
	TF_senddat(date);
}
void main ()
{
    wagh();
	Uart1234_Init();
	Init_NRF24L01_MA();
	Set_TxMode_MA();
	delay_ms(200);
	CLR_Buf1();
	while (1)
	{    
		if(key==0)
		{
			delay_ms(200);
		if(key==0)
		{
		 a++;
		}
		}
    switch(a)	
		{ 
			case 0 :  LCD2406("Power up!       Waitting cmd!   ");break;//等待
		    case 1 :  three(0x02);a++;LCD2406("Start Dancing........          ");delay_ms(1000);break;//启动舞蹈
			case 2 :  LCD2406("Dancing........                  ");break;//舞蹈
			
			case 3 :  three(0x01);a++;LCD2406("Emergency stop   button ...     ");delay_ms(1000);break;//紧急停止
			case 4 :  LCD2406("Stop dancing -  Waitting cmd.....     ");break;//等待

			case 5 :  a=0;break;
		}			
	}
}

主程序–主机器人

# include "uart.h"
# include "NARFL2401.h"
#include "asr.h"
# include "open.H"
#include "duoji.h"
extern  unsigned char cmd;

void main ()
{ 
	wagh();                       //IO初始化
	Uart1234_Init();              //四串口初始化
	Init_NRF24L01_MA();	          //初始化
	Set_RxMode_MA();	  				  //配置nRF24L01为接收模式
	zhongduanyi();                //中断初始化
	P2=0X00;
	start();
	while(1)
	{   
		switch (cmd)
			{
					case 0 :SendStringByUart1("0x00  ");break;                            //等待启动
					case 1 :SendStringByUart1("0x01  ");break;                            //紧急停止
					case 2 :SendStringByUart1("0x02  ");dancing();                        //舞蹈启动
					case 3 :SendStringByUart1("0x03  ");                                  //人机交互 
									human_computer();//语音交互
					case 4 :SendStringByUart1("0x04  ");  
									human_computer2();//视觉交互     
									break;          
					default :cmd=0;break;
			}
				delay_ms(200);
			}
}

主程序–2个从机器人

# include "uart.h"
# include "NARFL2401.h"
#include "asr.h"
# include "open.H"
#include "duoji.h"
extern  unsigned char cmd;

void main ()
{ 
	wagh();                       //IO初始化
	Uart1234_Init();              //四串口初始化
	Init_NRF24L01_MA();	          //初始化
	Set_RxMode_MA();	  				  //配置nRF24L01为接收模式
	zhongduanyi();                //中断初始化
	P2=0X00;
	start();
	while(1)
	{
			switch (cmd)
		{
				case 0 :SendStringByUart1("0x00  ");break;                            //等待启动
				case 1 :SendStringByUart1("0x01  ");break;                            //紧急停止
				case 2 :SendStringByUart1("0x02  "); dancing();
								break;       
				default :cmd=0;break;
		}
			delay_ms(200);
 }
}

从机器人没有设置交互功能,其他部分都和主机器人一样。

主程序中的分块程序

外部中断

void zhongduanyi()
{
	ES=1;
	IE2 |= 0x01;
	IE2 |= 0x08;
	IE2 |= 0x10;
	
	IE1  = 0;	     //外部中断1标志位
	EX1 = 1;	     //INT1 Enable
	IT1 = 1;		   //INT1 下降沿中断		
	EA = 1;		     //允许总中断
}

开启外部中断1接收无线信号。

舵机函数

使用数字舵机CDS5516。通过通信协议设置每个舵机的ID号,设置速度(速度过慢过快会使得舞蹈过程不够优美等;并且根据大赛要求需要控制舞蹈时长)、最大旋转角度(比如仿真人体结构,头部关节不可超过180度)

void dancing()
{
	while(cmd!=0x01)
	{
		static unsigned char y=0;
 	  switch(y)
   { 
 	   case 0:part_one();break;  //part_部分就是我们设置的舞蹈动作
	   case 1:part_two();break;
	   case 2:part_three();
       case 3:cmd=0x01;break;	 
	 }
	  y++;
 }
}

//舵机驱动程序
void a(unsigned char ID,unsigned short int speed,unsigned short int position)
{
	unsigned char temp_sum=0; 
  if (position>=0x3ff)
		  position=0x3ff;	
	if (speed>=0x3ff)
		  speed=0x3ff;		
	temp_sum=ID + 0x07 + 0x04 + 0x1E + position%256+position/256+speed%256+speed/256;
	temp_sum=~temp_sum;
	IE2 &= 0xF7; 			               // 串口3中断关闭
	OE2=0;
  OE1=1;
  SendDataByUart3(0XFF);
  SendDataByUart3(0XFF);
  SendDataByUart3(ID);
  SendDataByUart3(0X07);
  SendDataByUart3(0X04);
  SendDataByUart3(0X1E);
  SendDataByUart3(position%256);
	SendDataByUart3(position/256);	
	SendDataByUart3(speed%256);	
	SendDataByUart3(speed/256);	
	SendDataByUart3(temp_sum);
	IE2 |= 0x08;                     // 串口3中断打开
  OE2=1;
  OE1=0;
	delay_ms(100);
}

语音识别函数

void Voice_interaction()
{  
	 ASR_init();                            //语音识别模块初始化
	 ASR_init1();                           //灵敏度初始化
	 ASR_init2();                           //音量输入初始化
	 start_repeatedly_Speech_Recognition(); //启动连续识别
		while(cmd!=0x01)
		{ 
			if(Rec_Buf2[0]==0x5B)
			{
				delay_ms(500);                                     //等待接收三位数数据
				EA=0;                                              //关闭总中断
	      SendDataByUart1(Rec_Buf2[0])	;
        SendDataByUart1(Rec_Buf2[1])	;				
				switch (Rec_Buf2[1])
				{
					case 1:P2=~0X11;
					       Delay2500ms(); 
        				 P2=0X00 ;
					       break;                                     //可以
  				    case 2:P2=~0X12;
					       Delay5000ms();  
					       P2=0X00; 
					       break;                                     //我是17年入伍,目前为止,已经有两年军龄
          			case 3:P2=~0X13;
					       Delay10000ms(); 
					       P2=0X00; 
					       break;                                     //当兵首先对自己是一种锻炼,使我更加的自律,但更深层次是对国旗,对国家,对人民的信仰。
					case 4:P2=~0X14;
					       Delay6000ms();  
					       P2=0X00; 
					       break;                                      //我的老班长,当兵过程中,老班长就像一面旗帜,引领我们前进。
  					case 5:P2=~0X15;
					       Delay3500ms();  
					       P2=0X00; 
					       cmd=0x05;                                   //准备退出语音模块
					       break;                                      //好好学习,为国争光。
				}
				delay_ms(200);
				CLR_Buf2();
				EA=1;                                               //打开总中断
			}
		}
    ASR_end();                                             //退出语音识别
}

//启动连续语音识别函数start_repeatedly_Speech_Recognition()
void start_repeatedly_Speech_Recognition(void)
{ 
	IE2 &= 0xFE; 			                         // 串口2中断关闭
    SendDataByUart2(0xAB);
	SendDataByUart2(0xAB);
	SendDataByUart2(0x00);
	IE2 |= 0x01;                               // 串口2中断打开
  delay_ms(1000);	
}

无线发送/接收数据

//无线发送数据
Set_TxMode_MA();
void TF_senddat(unsigned char i)
{
	TxPayload[0] =i;
	if(NRF24L01_TxPacket_MA(TxPayload) == TX_OK)	  //如果发送成功
		{
		  led=~led;  			//无线发射成功 
      delay_ms(200);			
		 }
}
//无线接收数据
Set_RxMode_MA();
void RF_ReceDat()
  {  
		 if(NRF24L01_RxPacket_MA(RxPayload) == RX_OK)	//如果接收到数据
		   {	
				 cmd=RxPayload[0];
				 RxPayload[0]=0;
         delay_ms(200);				 
		   }		
 }

设置IO口

void wagh()
{
// P0 端口模式寄存器 1,设置为 0 表示准双向口 
// P0 端口模式寄存器 0,设置为 0 表示准双向口
    P0M1 = 0;	P0M0 = 0;	//设置P0.0~P0.7为准双向口  
	P1M1 = 0;	P1M0 = 0;	//设置P1.0~P1.7为准双向口
​	P2M1 = 0;	P2M0 = 0;	//设置P2.0~P2.7为准双向口 
​	P3M1 = 0;	P3M0 = 0;	//设置P3.0~P3.7为准双向口
​	P4M1 = 0;	P4M0 = 0;	//设置P4.0~P4.7为准双向口
​	P5M1 = 0;	P5M0 = 0;	//设置P5.0~P5.7为准双向口
}

使用for循环完成毫秒级别的延迟:

优点:简单易实现,不需要使用专门的硬件定时器或时钟;

缺点:1、精度不高(依赖于编译器的优化和 MCU 的时钟频率);2、使用阻塞式延迟,延迟期间,CPU 无法执行其他任务。

for循环延迟适用于需要简单延迟的嵌入式应用。

初始化UART四个串口

void Uart1234_Init(void)
{	
  // 选择 P1.6 和 P1.7 为 UART1 的引脚
  P_SW1 |= 0x00; // P_SW1 的低 2 位控制 UART1 引脚选择,0x00 表示选择 P1.6 和 P1.7
  P_SW1 &= 0xFF; // 确保 P1.6 和 P1.7 被正确选择为 UART1

  // 选择 P4.6 和 P4.7 为 UART2 的引脚
  P_SW2 |= S2_S; // P_SW2 的某一位控制 UART2 引脚选择,S2_S 是宏定义,表示选择 P4.6 和 P4.7

  // 初始化 UART1
  PCON &= 0x3F; // 清除 SMOD 和 SMOD0 位,选择 UART1 普通波特率
  SCON = 0x50;  // 设置 UART1 为 8 位可变波特率模式,允许接收
  AUXR |= 0x01; // 选择 Timer 2 作为 UART1 的波特率发生器

  // 初始化 UART2
  S2CON = 0x50; // 设置 UART2 为 8 位可变波特率模式,允许接收

  // 初始化 UART3
  S3CON |= 0x50; // 设置 UART3 为 8 位可变波特率模式,允许接收
  S3CON &= 0x70; // 选择 Timer 2 作为 UART3 的波特率发生器

  // 初始化 UART4
  S4CON |= 0x10; // 设置 UART4 为 8 位可变波特率模式,允许接收
  S4CON &= 0x30; // 选择 Timer 2 作为 UART4 的波特率发生器
  S4CON |= 0x40; // 选择 Timer 4 作为 UART4 的波特率发生器

  // 配置 Timer 2 作为波特率发生器
  AUXR |= 0x04; // 设置 Timer 2 时钟为 Fosc,1T 模式
  T2L = (65536 - (MAIN_Fosc / 4 / BAUD));
  T2H = (65536 - (MAIN_Fosc / 4 / BAUD)) >> 8; // 设置 Timer 2 的重装值
  AUXR |= 0x10; // 启动 Timer 2

  // 配置 Timer 3 和 Timer 4 作为波特率发生器
  T4T3M |= 0x22; // 设置 Timer 3 和 Timer 4 时钟为 Fosc,1T 模式
  T3L = (65536 - (MAIN_Fosc / 4 / BAUD3));
  T3H = (65536 - (MAIN_Fosc / 4 / BAUD3)) >> 8;
  T4L = (65536 - (MAIN_Fosc / 4 / BAUD4));
  T4H = (65536 - (MAIN_Fosc / 4 / BAUD4)) >> 8;
  T4T3M |= 0x88; // 启动 Timer 3 和 Timer 4
}

串口1、3是选择定时器2作为波特率发生器,串口4选择定时器4作为波特率发生器。

串口2没有设置定时器作为波特率发生器的原因可能是因为串口2的波特率生成方式不同于串口1、3和4。在这段代码中,串口2的波特率可能是通过其他方式生成的,而不是依赖于定时器。因此,在初始化串口2时,并没有设置定时器作为波特率发生器。

串口1发送字符串函数

//串口1发送字符函数
void SendDataByUart1(uint8 dat)
{
    SBUF = dat;                 //写数据到UART1数据寄存器
		while(TI == 0);             //在停止位没有发送时,TI为0即一直等待
		TI = 0;                     //清除TI位(该位必须软件清零)
}

//串口1发送字符串函数

void SendStringByUart1(uint8 *s)
{
	while(*s)
	{
		SendDataByUart1(*s++);       //将字符串中的字符一个一个发送
	}
}

SBUF是芯片头文件中定义好的。

sfr(Special Function Register)是一个关键字,即特殊功能寄存器。在单片机中,特殊功能寄存器是用于控制各种外设和功能的寄存器,如串口数据传输、定时器控制等。通过定义sfr,程序员可以直接访问。

//串行口特殊功能寄存器
sfr SCON        =   0x98;   //0000,0000 串口1控制寄存器
sbit SM0        =   SCON^7;
sbit SM1        =   SCON^6;
sbit SM2        =   SCON^5;
sbit REN        =   SCON^4;
sbit TB8        =   SCON^3;
sbit RB8        =   SCON^2;
sbit TI         =   SCON^1;
sbit RI         =   SCON^0;
sfr SBUF        =   0x99;   //xxxx,xxxx 串口1数据寄存器
sfr S2CON       =   0x9A;   //0000,0000 串口2控制寄存器
sfr S2BUF       =   0x9B;   //xxxx,xxxx 串口2数据寄存器
sfr S3CON       =   0xAC;   //0000,0000 串口3控制寄存器
sfr S3BUF       =   0xAD;   //xxxx,xxxx 串口3数据寄存器
sfr S4CON       =   0x84;   //0000,0000 串口4控制寄存器
sfr S4BUF       =   0x85;   //xxxx,xxxx 串口4数据寄存器

握手函数

bit Hand1(unsigned char *a)
{ 
    if(strstr(Rec_Buf1, a) != NULL)  // 判断字符串 a 是否在 Rec_Buf1 中
        return 1;                    // 如果字符串 a 是 Rec_Buf1 的子串,返回 1
    else
        return 0;                    // 如果字符串 a 不是 Rec_Buf1 的子串,返回 0
}

bit 类型通常在 8051 系列单片机中使用,表示一个二进制位(0 或 1)

strstr 是标准 C 库函数,strstr 是标准 C 库函数

串口中断函数

//定义了一个 UART1 的中断服务程序.interrupt UART1_VECTOR 指定这个函数是 UART1 的中断处理程序,UART1_VECTOR 是 UART1 中断向量,using 1 表示使用寄存器组 1 来处理这个中断。
void Uart1() interrupt UART1_VECTOR using 1 
{
	ES = 0;                   // 串口1中断关闭,防止在处理过程中再次触发中断,确保数据处理的完整性。
	if (RI)                       //串行接收到停止位的中间时刻时,该位置1
  {
      RI = 0;                   //清除RI位 (该位必须软件清零)
			Rec_Buf1[i] = SBUF;       //把串口1缓存SBUF寄存器数据依次存放到数组Rec_Buf1中
			i++;                      
	    if(i>Buf_Max)             //接收数大于定义接收数组最大个数时,覆盖接收数组之前值
				{
					i = 0;                 
				}           
   }
   if (TI)                    //在停止位开始发送时,该位置1
   {
      TI = 0;                 //清除TI位(该位必须软件清零)
   }
	 ES =  1;                   // 串口1中断打开
}
  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值