首页 > 分享 > 如何制作一款电子桌宠小狗(软件部分)开源(文章中内置代码附有详细的解释对各专业名词以及应用场景进行说明)

如何制作一款电子桌宠小狗(软件部分)开源(文章中内置代码附有详细的解释对各专业名词以及应用场景进行说明)

文章目录 概要整体架构流程技术名词解释技术细节小结

概要

本项目采用模块化编程

一、USART1 与 USART3 的运用

USART1 和 USART3 主要负责数据的传输。我们可以通过它们与其他设备进行通信,实现小狗与外界的互动(STM32 的 USART1 和 USART3 通常会有默认的引脚分配)。见下图

二、PWM 的功能实现

利用 PWM 技术,我们可以精确控制小狗的动作,比如通过调整占空比来模拟小狗的行走效果。

三、TIM 的应用

PWM(脉宽调制)的实现通常需要使用定时器(TIM)来提供精确的时间基准。定时器可以产生周期性的时钟信号,通过对定时器的配置,可以控制 PWM 信号的周期和占空比。利用定时器的计数功能,我们可以在特定的时间点切换输出电平,从而实现不同占空比的 PWM 信号。

四、I2C 控制 OLED

I2C 是连接 OLED 显示屏的重要方式。通过 I2C 协议,我们能够将小狗的图像和动画数据准确地传输到显示屏上,使其生动呈现。

整体架构流程

通过串口对蓝牙和语音识别功能进行接收和发送,从而实现对单片机的控制。通过 PWM 实现对舵机的控制。通过II2通信对 OLED 屏进行控制。

技术名词解释

PWM:脉冲宽度调制,通过调节脉冲的宽度来控制信号的平均值。

TIM:定时器,用于产生定时信号或进行时间测量。

USART:通用同步/异步收发器,用于串行数据通信。“TX”的全称是“Transmit”(发送),“RX”的全称是“Receive”(接收)。

II2:这个不太明确具体含义,可能是某种特定的标识或参数。

使能:意思是使某一功能或操作得以启用、生效或实现。

它通常用于描述开启或激活某个特定的能力或机制。

遍历:按照一定的规则依次访问和处理数据结构中的每个元素的过程。

它就像是逐一检查数据结构中的每一项,确保不遗漏任何一个元素。

常见的数据结构遍历包括数组遍历、链表遍历、树结构遍历等。通过遍历,可以对数据进行各种操作,如读取、修改、统计等。

技术细节

蓝牙部分

在定义 TX(发送)引脚时,通常初始化为推挽输出模式;而定义 RX(接收)引脚时,一般初始化为浮空输入或上拉输入模式。设置PA9,PA10分别为TX,RX.

uint8_t USART1_RxData;//存储从串口接收到的数据的变量

uint8_t USART1_RxFlag;//指示串口接收状态的变量,通常用于表示是否有新的数据接收到、接收是否完成等状态信息。

void USART1_Init(void)

{

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟

GPIO_InitTypeDef GPIO_InitStructure; //定义结构体

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出,TX

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//表示输入上拉模式。

在这种模式下,对应引脚被配置为输入模式,并且内部有上拉电阻,使得引脚在没有外部输入时保持高电平。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = 9600;//波特率

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//这里是选择不用硬件流控制,硬件流控制是一种数据传输的控制方式。它通过使用特定的硬件信号线(如 RTS/CTS 等)来在发送方和接收方之间进行通信,以协调数据的发送和接收速度,确保数据传输的准确性和稳定性,避免数据丢失或错乱。以下情况通常会使用硬件流控制:

高速数据传输:当数据传输速率较高,需要更精确地控制数据发送和接收时。

对数据准确性要求高:在一些关键应用中,为了保证数据的完整性,会采用硬件流控制。

长距离传输:在信号容易受到干扰的长距离传输中,硬件流控制有助于提高传输的可靠性。

USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//两种模式均选择

USART_InitStructure.USART_Parity = USART_Parity_No;//Parity 的意思是奇偶,这里是选择不需要奇偶校验,奇偶校验是一种简单的校验方法,用于检测数据在传输或存储过程中是否发生错误。

它通过在数据编码中添加一位校验位,使数据中“1”的个数为奇数(奇校验)或偶数(偶校验)。接收方通过检查校验位来判断数据是否正确。

USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位1位

USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位8位,8b就是8bit。

USART_Init(USART1, &USART_InitStructure);

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置 NVIC 优先级分组为 2。

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//使能中断、抢占优先级和响应优先级都设置为 1。抢占优先级:指的是中断能够抢占其他低优先级中断的能力。具有较高抢占优先级的中断可以先被执行,从而中断正在执行的低优先级中断。

响应优先级:也称为子优先级,它决定了在具有相同抢占优先级的中断同时发生时的执行顺序。响应优先级较高的中断会先被处理。对比串口3,第一个串口的抢占优先级和响应优先级都为 1,意味着它在中断处理中的优先级相对较低。

第三个串口的抢占优先级为 1,与第一个串口相同,但响应优先级设置为 3,相对较高。这意味着在相同抢占优先级的中断中,第二个串口的中断会优先得到响应和处理

NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART1, ENABLE);//使能

}

//下面是一些函数

USART1_SendByte 和 USART1_SendArray 函数用于通过串口发送单个字节或数组数据。

USART1_SendString 函数用于发送字符串数据。

Serial_Pow 函数用于计算幂运算。

USART1_SendNumber 函数用于发送数字。

USART1_fputc 函数用于将字符输出到串口,使其可以像标准输出一样使用。

USART1_Printf 函数用于格式化并发送数据到串口。

USART1_GetRxFlag 和 USART1_GetRxData 函数用于获取接收标志和接收数据。

USART1_IRQHandler 函数是串口中断处理函数,用于处理接收数据的事件。

void USART1_SendByte(uint8_t Byte)

{

USART_SendData(USART1, Byte);//将指定的字节数据写入串口 1 的发送寄存器。

while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//这是一个等待循环,它会一直等待,直到串口 1 的发送缓冲区为空(即发送完成标志 USART_FLAG_TXE 被设置)。

}

void USART1_SendArray(uint8_t *Array, uint16_t Length)

{

uint16_t i;

for (i = 0; i < Length; i ++)

{

USART1_SendByte(Array[i]);

}

}//uint16_t i:定义一个循环变量 i。

for (i = 0; i < Length; i ++):通过循环,从数组的第一个元素开始,依次将每个元素作为一个字节发送出去。

在每次循环中,通过调用 USART1_SendByte(Array[i]),将当前数组元素发送出去。

void USART1_SendString(char *String)

{

uint8_t i;

for (i = 0; String[i] != ''; i ++)

{

USART1_SendByte(String[i]);

}

}

//uint8_t i:定义一个用于遍历字符串的索引变量i。

for (i = 0; String[i]!= ''; i ++):这是一个循环,从字符串的开头开始,一直到遇到字符串的结束标志('')为止。

在循环中,通过USART1_SendByte(String[i]),将字符串的每个字符依次作为一个字节发送出去。这样就实现了将整个字符串通过串口发送出去的功能。

uint32_t Serial_Pow(uint32_t X, uint32_t Y)

{

uint32_t Result = 1;

while (Y --)

{

Result *= X;

}

return Result;

}

//Serial_Pow 函数用于计算一个数的指定次幂。

uint32_t Result = 1:初始化结果为 1。

while (Y --):这是一个循环,循环次数为 Y 次。

在循环中,通过不断将当前结果乘以 X,来计算幂值。每次循环后,Y 的值减 1。

当循环结束后,返回计算得到的幂值。

void USART1_SendNumber(uint32_t Number, uint8_t Length)

{

uint8_t i;

for (i = 0; i < Length; i ++)

{

USART1_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');

}

}

//uint8_t i:定义一个循环变量 i。

for (i = 0; i < Length; i ++):通过循环,从数字的高位到低位,逐位处理。

在每次循环中,计算当前位的数值,即 Number 除以 Serial_Pow(10, Length - i - 1) 取余后再加上 '0',将其转换为字符。然后使用 USART1_SendByte 发送该字符。这样就实现了将数字按位发送的效果。

int USART1_fputc(int ch, FILE *f)

{

USART1_SendByte(ch);

return ch;

}

//USART1_fputc 函数:

USART1_SendByte(ch):将输入的字符 ch 通过 USART1_SendByte 发送出去。

return ch:返回输入的字符。

void USART1_Printf(char *format, ...)

{

char String[100];

va_list arg;

va_start(arg, format);

vsprintf(String, format, arg);

va_end(arg);

USART1_SendString(String);

}//定义一个字符数组 String[100] 用于存储格式化后的字符串。

使用 va_list 和相关函数来处理可变参数,将格式化后的字符串存储到 String 中。

最后通过 USART1_SendString 将格式化后的字符串发送出去

uint8_t USART1_GetRxFlag(void)

{

if (USART1_RxFlag == 1)

{

USART1_RxFlag = 0;

return 1;

}

return 0;

}

//检查 USART1_RxFlag 是否为 1,如果是则将其重置为 0,并返回 1,表示接收到数据。

uint8_t USART1_GetRxData(void)

{

return USART1_RxData;

}//返回接收到的串口数据 USART1_RxData。

void USART1_IRQHandler(void)

{

if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)

{

USART1_RxData = USART_ReceiveData(USART1);

USART1_RxFlag = 1;

USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除中断挂起位。

}

}

//在中断处理函数中,当接收到串口数据(USART_IT_RXNE 标志位被设置为 SET)时,获取接收到的数据并设置 USART1_RxFlag 为 1。

语言控制部分(与上文代码大体结构相同,上文代码中均有解释,此段代码不做解释,参考上文即可理解)

uint8_t USART3_RxData;

uint8_t USART3_RxFlag;

void USART3_Init(void)

{

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;

GPIO_Init(GPIOB, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = 9600;

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

USART_InitStructure.USART_Parity = USART_Parity_No;

USART_InitStructure.USART_StopBits = USART_StopBits_1;

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

USART_Init(USART3, &USART_InitStructure);

USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;

NVIC_Init(&NVIC_InitStructure);

USART_Cmd(USART3, ENABLE);

}

uint8_t USART3_GetRxFlag(void)

{

if (USART3_RxFlag == 1)

{

USART3_RxFlag = 0;

return 1;

}

return 0;

}

uint8_t USART3_GetRxData(void)

{

return USART3_RxData;

}

void USART3_IRQHandler(void)

{

if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET)

{

USART3_RxData = USART_ReceiveData(USART3);

USART3_RxFlag = 1;

USART_ClearITPendingBit(USART3, USART_IT_RXNE);

}

}

OLED部分

因内容太多,放于下一期文章,本文侧重点在于控制系统

舵机部分

舵机原理见(【STM32(意法半导体推出的32位微控制器)通过HC-05主从机一体蓝牙模块实现对舵机(SG90)的控制(含源码) - CSDN App】http://t.csdnimg.cn/O6hDl)

PWM、Servo、Movement三个文件共同为驱动舵机服务

PA6:右前脚,PA7:右后脚,PB0:左前脚,PB1:左后脚

PWM

void PWM_Init(void)

{

// 使能 TIM3 的时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

// 使能 GPIOA 的时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 使能 GPIOB 的时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

// GPIO 初始化结构体设置

GPIO_InitTypeDef GPIO_InitStructure;

// 设置 GPIO 模式为复用推挽输出

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

// 设置 GPIO 引脚为 PA6 和 PA7

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;

// 设置 GPIO 速度为 50MHz

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

// 初始化 GPIOA

GPIO_Init(GPIOA, &GPIO_InitStructure);

// 设置 GPIO 引脚为 PB0 和 PB1

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;

// 初始化 GPIOB

GPIO_Init(GPIOB, &GPIO_InitStructure);

// 配置 TIM3 内部时钟

TIM_InternalClockConfig(TIM3);

// TIM 时间基准初始化结构体设置

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

// 设置时钟分频为 1

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

// 设置计数器模式为向上计数

TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;

// 设置周期为 20000 - 1

TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;

// 设置预分频器为 72 - 1

TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;

// 设置重复计数器为 0

TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;

// 初始化 TIM3 的时间基准

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

// TIM 输出比较初始化结构体设置

TIM_OCInitTypeDef TIM_OCInitStructure;

// 结构体初始化

TIM_OCStructInit(&TIM_OCInitStructure);

// 设置输出比较模式为 PWM1

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

// 设置输出极性为高

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

// 设置输出使能为启用

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

// 设置脉冲宽度为 0

TIM_OCInitStructure.TIM_Pulse = 0;

// 初始化 TIM3 的通道 2

TIM_OC2Init(TIM3, &TIM_OCInitStructure);

// 初始化 TIM3 的通道 3

TIM_OC3Init(TIM3, &TIM_OCInitStructure);

// 初始化 TIM3 的通道 1

TIM_OC1Init(TIM3, &TIM_OCInitStructure);

// 初始化 TIM3 的通道 4

TIM_OC4Init(TIM3, &TIM_OCInitStructure);

// 使能 TIM3

TIM_Cmd(TIM3, ENABLE);

}

void PWM_SetCompare1(uint16_t Compare)

{

TIM_SetCompare1(TIM3, Compare);

}

void PWM_SetCompare2(uint16_t Compare)

{

TIM_SetCompare2(TIM3, Compare);

}

void PWM_SetCompare3(uint16_t Compare)

{

TIM_SetCompare3(TIM3, Compare);

}

void PWM_SetCompare4(uint16_t Compare)

{

TIM_SetCompare4(TIM3, Compare);

}

//设置比较值的主要目的是控制 PWM(脉冲宽度调制)信号的占空比。

通过改变比较值,可以调整输出信号高电平的持续时间,从而实现对被控对象的精确控制,比如调节电机的转速、灯光的亮度等。不同的比较值对应着不同的占空比,进而影响输出的效果。

SERVO

Angle 是舵机角度,范围:0~180

void Servo_Init(void) // 定义 Servo_Init 函数

{

PWM_Init(); // 调用 PWM_Init 函数进行初始化

}

void Servo_SetAngle1(float Angle) // 定义 Servo_SetAngle1 函数,用于设置角度 1

{

PWM_SetCompare1(Angle / 180 * 2000 + 500);// 根据输入的角度计算比较值并设置

}

void Servo_SetAngle2(float Angle) // 定义 Servo_SetAngle2 函数,用于设置角度 2

{

PWM_SetCompare2(Angle / 180 * 2000 + 500);// 同上

}

void Servo_SetAngle3(float Angle) // 定义 Servo_SetAngle3 函数,用于设置角度 3

{

PWM_SetCompare3(Angle / 180 * 2000 + 500);// 同上

}

void Servo_SetAngle4(float Angle) // 定义 Servo_SetAngle4 函数,用于设置角度 4

{

PWM_SetCompare4(Angle / 180 * 2000 + 500);// 同上

}

uint8_t Servo_GetAngle1(void) // 定义 Servo_GetAngle1 函数,用于获取角度 1

{

return (TIM_GetCapture1(TIM3) - 500) * 180 / 2000; // 通过定时器捕获值计算并返回角度

}

uint8_t Servo_GetAngle2(void) // 定义 Servo_GetAngle2 函数,用于获取角度 2

{

return (TIM_GetCapture2(TIM3) - 500) * 180 / 2000; // 同上

}

uint8_t Servo_GetAngle3(void) // 定义 Servo_GetAngle3 函数,用于获取角度 3

{

return (TIM_GetCapture3(TIM3) - 500) * 180 / 2000; // 同上

}

uint8_t Servo_GetAngle4(void) // 定义 Servo_GetAngle4 函数,用于获取角度 4

{

return (TIM_GetCapture4(TIM3) - 500) * 180 / 2000; // 同上

}

//这样的计算是为了将角度范围转换为相应的定时器比较值,从而实现对 PWM 信号占空比的精确控制,以达到控制伺服o 电机转动角度的目的。

MOVEMENT(此段函数是设置一些角度)

int i,j;

int movedelay=150;

uint8_t angle_1, angle_2, angle_3, angle_4;

void move_stand(void){

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(500);

}

void move_forward(void){

Servo_SetAngle1(135);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle2(45);

Servo_SetAngle3(135);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

Servo_SetAngle2(135);

Servo_SetAngle3(45);

Delay_ms(movedelay);

Servo_SetAngle1(45);

Servo_SetAngle4(135);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

}

void move_behind(void){

Servo_SetAngle1(45);

Servo_SetAngle4(135);

Delay_ms(movedelay);

Servo_SetAngle2(135);

Servo_SetAngle3(45);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

Servo_SetAngle2(45);

Servo_SetAngle3(135);

Delay_ms(movedelay);

Servo_SetAngle1(135);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

}

void move_right(void){

Servo_SetAngle1(45);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle2(135);

Servo_SetAngle3(135);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

}

void move_left(void)

{

Servo_SetAngle2(135);

Servo_SetAngle3(135);

Delay_ms(movedelay);

Servo_SetAngle1(45);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

}

void move_hello(void)

{

for(i=0;i<20;i++)//

{

Servo_SetAngle1(90-i);

Servo_SetAngle2(90+i);

Servo_SetAngle4(90-i);

Delay_ms(4);

}

for(i=0;i<60;i++)

{

Servo_SetAngle2(110+i);

Servo_SetAngle4(70-i);

Delay_ms(4);

}

for(i=0;i<60;i++)

{

Servo_SetAngle1(70+i);

Delay_ms(4);

}

Delay_ms(50);

Servo_SetAngle1(180);

Delay_ms(500);

Servo_SetAngle1(130);

Delay_ms(500);

Servo_SetAngle1(180);

Delay_ms(500);

Servo_SetAngle1(130);

Delay_ms(500);

Servo_SetAngle1(70);

Delay_ms(500);

}

void move_shake_qianhou(void)

{

Servo_SetAngle2(135);

Servo_SetAngle1(135);

Servo_SetAngle3(45);

Servo_SetAngle4(45);

Delay_ms(150);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(150);

Servo_SetAngle2(45);

Servo_SetAngle1(45);

Servo_SetAngle3(135);

Servo_SetAngle4(135);

Delay_ms(150);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(150);

}

void move_shake_zuoyou(void)

{

Servo_SetAngle1(135);

Servo_SetAngle2(135);

Delay_ms(movedelay);

Servo_SetAngle3(135);

Servo_SetAngle4(135);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Delay_ms(movedelay);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle3(45);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle1(45);

Servo_SetAngle2(45);

Delay_ms(movedelay);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Delay_ms(movedelay);

}

void move_dance(void)

{

Servo_SetAngle1(135);

Servo_SetAngle2(135);

Delay_ms(movedelay);

Servo_SetAngle3(135);

Servo_SetAngle4(135);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Delay_ms(movedelay);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle2(135);

Servo_SetAngle1(135);

Servo_SetAngle3(45);

Servo_SetAngle4(45);

Delay_ms(150);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(150);

Servo_SetAngle3(45);

Servo_SetAngle4(45);

Delay_ms(movedelay);

Servo_SetAngle1(45);

Servo_SetAngle2(45);

Delay_ms(movedelay);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(movedelay);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Delay_ms(movedelay);

Servo_SetAngle2(45);

Servo_SetAngle1(45);

Servo_SetAngle3(135);

Servo_SetAngle4(135);

Delay_ms(150);

Servo_SetAngle1(90);

Servo_SetAngle2(90);

Servo_SetAngle3(90);

Servo_SetAngle4(90);

Delay_ms(150);

}

void move_head_up(void)

{

for(i=0;i<20;i++)

{

Servo_SetAngle1(90-i);

Servo_SetAngle3(90+i);

Delay_ms(10);

}

for(i=0;i<65;i++)

{

Servo_SetAngle2(90+i);

Servo_SetAngle4(90-i);

Delay_ms(10);

}

}

void move(uint16_t set1 , uint16_t set2 , uint16_t set3 , uint16_t set4 , uint16_t speed){

angle_1 = Servo_GetAngle1();

angle_2 = Servo_GetAngle2();

angle_3 = Servo_GetAngle3();

angle_4 = Servo_GetAngle4();

while (angle_1 != set1 || angle_3 != set3 || angle_2 != set2 || angle_4 != set4) {

if (angle_1 > set1) {

--angle_1;

Servo_SetAngle1(angle_1);

} else if (angle_1 < set1) {

++angle_1;

Servo_SetAngle1(angle_1);

}

if (angle_3 > set3) {

--angle_3;

Servo_SetAngle3(angle_3);

} else if (angle_3 < set3) {

++angle_3;

Servo_SetAngle3(angle_3);

}

if (angle_2 > set2) {

--angle_2;

Servo_SetAngle2(angle_2);

} else if (angle_2 < set2) {

++angle_2;

Servo_SetAngle2(angle_2);

}

if (angle_4 > set4) {

--angle_4;

Servo_SetAngle4(angle_4);

} else if (angle_4 < set4) {

++angle_4;

Servo_SetAngle4(angle_4);

}

Delay_ms(1000/speed);

}

}

void move_slow_stand(uint8_t previous_mode){

if (previous_mode == '0')

return;

angle_1 = Servo_GetAngle1();

angle_2 = Servo_GetAngle2();

angle_3 = Servo_GetAngle3();

angle_4 = Servo_GetAngle4();

while (angle_1 != 90 || angle_3 != 90 || angle_2 != 90 || angle_4 != 90) {

if (angle_1 > 90) {

--angle_1;

Servo_SetAngle1(angle_1);

} else if (angle_1 < 90) {

++angle_1;

Servo_SetAngle1(angle_1);

}

//

if (angle_3 > 90) {

--angle_3;

Servo_SetAngle3(angle_3);

} else if (angle_3 < 90) {

++angle_3;

Servo_SetAngle3(angle_3);

}

if (angle_2 > 90) {

--angle_2;

Servo_SetAngle2(angle_2);

} else if (angle_2 < 90) {

++angle_2;

Servo_SetAngle2(angle_2);

}

//

if (angle_4 > 90) {

--angle_4;

Servo_SetAngle4(angle_4);

} else if (angle_4 < 90) {

++angle_4;

Servo_SetAngle4(angle_4);

}

Delay_ms(10);

}

}

void move_stretch(void){

for(i=0;i<65;i++)

{

Servo_SetAngle2(90+i);

Servo_SetAngle4(90-i);

Delay_ms(5);

}

for(i=0;i<20;i++)

{

Servo_SetAngle1(90-i);

Servo_SetAngle3(90+i);

Delay_ms(5);

}

Delay_ms(1000);

for(i=0;i<60;i++)

{

Servo_SetAngle1(70+i);

Delay_ms(4);

}

Delay_ms(1000);

Servo_SetAngle1(180);

Delay_ms(500);

Servo_SetAngle1(130);

Delay_ms(500);

Servo_SetAngle1(180);

Delay_ms(500);

Servo_SetAngle1(130);

Delay_ms(500);

Servo_SetAngle1(70);

Delay_ms(5);

}

void move_two_hands(void){

Servo_SetAngle3(20);

Servo_SetAngle2(20);

Delay_ms(200);

Servo_SetAngle3(90);

Servo_SetAngle2(90);

Delay_ms(200);

Servo_SetAngle1(160);

Servo_SetAngle4(160);

Delay_ms(200);

Servo_SetAngle1(90);

Servo_SetAngle4(90);

Delay_ms(200);

}

void lan_yao(void){

for(i=0;i<75;i++)

{

Servo_SetAngle1(90+i);

Servo_SetAngle3(90-i);

Servo_SetAngle2(90+i/2);

Servo_SetAngle4(90-i/2);

Delay_ms(5);

}

Delay_ms(movedelay*50);

for(i=0;i<75;i++)

{

Servo_SetAngle1(165-i);

Servo_SetAngle3(15+i);

Servo_SetAngle2(127-i/2);

Servo_SetAngle4(53+i/2);

Delay_ms(5);

}

Delay_ms(movedelay);

}

void move_sleep_p(void) {

for(i=0;i<75;i++)

{

Servo_SetAngle1(90+i);

Servo_SetAngle3(90-i);

Delay_ms(10);

}

for(i=0;i<75;i++)

{

Servo_SetAngle4(90+i);

Servo_SetAngle2(90-i);

Delay_ms(10);

}

}

void move_sleep_w(void) {

for(i=0;i<75;i++)

{

Servo_SetAngle3(90+i);

Servo_SetAngle1(90-i);

Delay_ms(10);

}

for(i=0;i<75;i++)

{

Servo_SetAngle2(90+i);

Servo_SetAngle4(90-i);

Delay_ms(10);

}

}

各器件配合部分(各种器件连在一起完成某个动作)

extern uint8_t move_mode1 ;

extern uint8_t move_mode3 ;

extern uint8_t move_mode ;

extern uint8_t previous_mode ;

void mode_forward(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP2);

move_forward();

LED13_Turn();

previous_mode = move_mode;

}

void mode_behind(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP2);

move_behind();

LED13_Turn();

previous_mode = move_mode;

}

void mode_left(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP3);

move_left();

LED13_Turn();

previous_mode = move_mode;

}

void mode_right(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP4);

move_right();

LED13_Turn();

previous_mode = move_mode;

}

void mode_swing_qianhou(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP11);

move_shake_qianhou();

LED13_Turn();

previous_mode = move_mode;

}

void mode_swing_zuoyou(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP11);

move_shake_zuoyou();

LED13_Turn();

previous_mode = move_mode;

}

void mode_dance(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP5);

move_dance();

LED13_Turn();

previous_mode = move_mode;

}

void mode_stand(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_stand();

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_slowstand(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_strech(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

OLED_ShowImage(0, 0, 128, 64, BMP2);

move_stretch();

OLED_ShowImage(0, 0, 128, 64, BMP12);

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_hello(void)

{

if (previous_mode != '5' && previous_mode != 'D') {

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

}

OLED_ShowImage(0, 0, 128, 64, BMP12);

int i;

for(i=0;i<20;i++)

{

Servo_SetAngle1(90-i);

Servo_SetAngle2(90+i);

Servo_SetAngle4(90-i);

Delay_ms(7);

}

for(i=0;i<40;i++)

{

Servo_SetAngle2(110+i);

Servo_SetAngle4(70-i);

Delay_ms(7);

}

for(i=0;i<60;i++)

{

Servo_SetAngle1(70+i);

Delay_ms(4);

}

Delay_ms(50);

Servo_SetAngle1(180);

Delay_ms(400);

Servo_SetAngle1(130);

Delay_ms(400);

Servo_SetAngle1(180);

Delay_ms(400);

Servo_SetAngle1(130);

Delay_ms(400);

Servo_SetAngle1(70);

Delay_ms(500);

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_twohands(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_stand();

move_two_hands();

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_lanyao(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

OLED_ShowImage(0, 0, 128, 64, BMP9);

lan_yao();

OLED_ShowImage(0, 0, 128, 64, BMP1);

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_headup(void)

{

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

OLED_ShowImage(0, 0, 128, 64, BMP10);

move_head_up();

LED13_Turn();

previous_mode = move_mode;

move_mode = '0';

}

void mode_sleeppa(void)

{

if (previous_mode != '5' && previous_mode != 'q') {

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

}

if (rand()%2) {

OLED_ShowImage(0, 0, 128, 64, BMP6);

}

else{

OLED_ShowImage(0, 0, 128, 64, BMP8);

}

move_sleep_p();

previous_mode = move_mode;

move_mode = '0';

}

void mode_sleepwo(void)

{

if (previous_mode != '5' && previous_mode != 'q') {

OLED_ShowImage(0, 0, 128, 64, BMP1);

move_slow_stand(previous_mode);

Delay_s(1);

}

if (rand()%2) {

OLED_ShowImage(0, 0, 128, 64, BMP6);

}

else{

OLED_ShowImage(0, 0, 128, 64 , BMP8);

}

move_sleep_w();

previous_mode = move_mode;

move_mode = '0';

}

主函数逻辑

// 定义变量

uint8_t RxData; // 接收数据变量

uint8_t move_mode1 = '0'; // 模式 1

uint8_t move_mode3 = '0'; // 模式 3

uint8_t move_mode = '0'; // 当前模式

uint8_t previous_mode = '0'; // 上一个模式

int main(void) // 主函数

{

LED_Init(); // 初始化 LED

OLED_Init(); // 初始化 OLED

USART1_Init(); // 初始化 USART1

USART3_Init(); // 初始化 USART3

Servo_Init(); // 初始化舵机

OLED_Clear(); // 清屏

OLED_ShowImage(0, 0, 128, 64, BMP1); // 显示图像

mode_stand(); // 进入站立模式

while (1) // 主循环

{

move_mode1 = USART1_GetRxData(); // 获取 USART1 接收的数据

move_mode3 = USART3_GetRxData(); // 获取 USART3 接收的数据

if (USART1_GetRxFlag()) // 如果 USART1 有接收标志

{

move_mode = move_mode1; // 更新当前模式为模式 1

}

else if (USART3_GetRxFlag()) // 如果 USART3 有接收标志

{

move_mode = move_mode3; // 更新当前模式为模式 3

}

if (move_mode == 'f') // 如果当前模式为前进

{

mode_forward(); // 执行前进模式

}

else if (move_mode == 'b') // 如果当前模式为后退

{

mode_behind(); // 执行后退模式

}

else if (move_mode == 'l') // 如果当前模式为左移

{

mode_left(); // 执行左移模式

}

else if (move_mode == 'r') // 如果当前模式为右移

{

mode_right(); // 执行右移模式

}

else if (move_mode == 'w') // 如果当前模式为前后摆动

{

mode_swing_qianhou(); // 执行前后摆动模式

}

else if (move_mode == 'z') // 如果当前模式为左右摆动

{

mode_swing_zuoyou(); // 执行左右摆动模式

}

else if (move_mode == 'd') // 如果当前模式为舞蹈

{

mode_dance(); // 执行舞蹈模式

}

else if (move_mode == '5') // 如果当前模式为站立

{

mode_stand(); // 执行站立模式

}

else if (move_mode == 'q' && previous_mode!= '0') // 如果当前模式为缓慢站立且上一个模式不为 0

{

mode_slowstand(); // 执行缓慢站立模式

}

else if (move_mode =='s' && previous_mode!= '') // 如果当前模式为伸展且上一个模式不为伸展

{

mode_strech(); // 执行伸展模式

}

else if (move_mode == 'j') // 如果当前模式为双手

{

mode_twohands(); // 执行双手模式

}

else if (move_mode == 'y') // 如果当前模式为懒腰

{

mode_lanyao(); // 执行懒腰模式

}

else if (move_mode == '1') // 如果当前模式为抬头

{

mode_headup(); // 执行抬头模式

}

else if (move_mode == 'p' && previous_mode!= 'p') // 如果当前模式为睡眠且上一个模式不为睡眠

{

mode_sleeppa(); // 执行睡眠模式

}

else if (move_mode == '2' && previous_mode!= '2') // 如果当前模式为另一种睡眠且上一个模式不为另一种睡眠

{

mode_sleepwo(); // 执行另一种睡眠模式

}

}

}

LD3320串口版语音模块(需单独烧录)

放置开源代码中,不进行解释,可参考文章(http://【STM32F103C8T6与LD3320进行串口通讯控制LED灯的亮灭 - CSDN App】http://t.csdnimg.cn/8qo2F)

开源代码

通过网盘分享的文件:软件.zip
链接: https://pan.baidu.com/s/1lWq7ZRIU3ooZQUHlGB4-mg?pwd=2003 提取码: 2003

相关知识

如何制作一款电子桌宠小狗(硬件部分)开源
开源桌宠软件VPet:WPF应用中的虚拟宠物伴侣
C#开源桌宠模拟器:WPF中内置互动虚拟宠物
探秘 VPet:一个开源的虚拟宠物应用
开源之魅:桌面宠物软件在WPF应用中的无缝集成
打造个性化的虚拟桌宠:开源WPF应用程序VPet
基于C#制作一个桌面宠物
【开源】如何做出自己的第一款电子桌面宠物小呆
2023XuperCore开源区块链创新赛
安桌开源项目

网址: 如何制作一款电子桌宠小狗(软件部分)开源(文章中内置代码附有详细的解释对各专业名词以及应用场景进行说明) https://m.mcbbbk.com/newsview598843.html

所属分类:萌宠日常
上一篇: “异宠”非法交易,平台为何老是管
下一篇: 【HTML期末学生大作业】 制作