STM32HAL库 总线舵机驱动库的编写

news2024/11/29 8:41:08

STM32 HAL库 总线舵机驱动库的编写

文章目录

  • STM32 HAL库 总线舵机驱动库的编写
    • 1 理论基础
      • 1.1 硬件
      • 1.2 电路图
      • 1.3 原理
      • 1.4 通信协议
    • 2 程序编写
      • 2.1 cube mx设置
        • (1)USART1设置
        • (2)USART3设置
      • 2.2 程序编写
        • (1)bsp_key.c
        • (2)bsp_key.h
        • (3)bsp_uart.c
        • (4)bsp_uart.h
        • (5)bsp_bool.h
        • (6)bsp_serial_servo.c
        • (7)bsp_serial_servo.h
        • (8)bsp.c
        • (9)bsp.h


1 理论基础

1.1 硬件

版本一:幻尔串行总线舵机、亚博智能 ROS机器人控制器STM32主控

在这里插入图片描述
在这里插入图片描述

版本二:串行总线舵机、任意型号的STM32开发板、SN74AHC1G04DBV(单路反相器)、74HC126D(缓冲器)、MF-MSMF300自恢复保险丝等

1.2 电路图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gy0zoRW-1684223384004)(C:\Users\77454\AppData\Roaming\Typora\typora-user-images\image-20230515093301452.png)]

1.3 原理

本次所用的舵机为串行总线舵机,采用异步串行总线通讯方式,理论多至 253 个机器人舵机可以通过总线组 成链型,一条总线上允许挂载多个舵机,且每个舵机均有一个唯一ID(0~253),通过 UART 异步串行接口统一控制。

每个舵机可以设定不同的节点地址,多个舵机可以统一运动也可以单个独立控制。通过异步串行接口与用户的上位机(控制器或PC机)通讯,可对其进行参数设置、功能控制。通过异步串行接口发送指令,可以设置为电机控制模式或位置控制模式。

在电机控制模式下,可以作为直流减速电机使用,速度可调;在位置控制模式下,拥有 0-240° 的转动范围,外加±30°的偏差可调范围,在此范围内具备精确位置控制性能,速度可调。只要符合协议的半双工UART异步串行接口都可以和舵机进行通讯,对舵机进行各种控制。

原理图如上图所示,舵机用程序代码对UART(本次示例中使用USART3)异步串口进行时序控制,实现半双工异步串行总线通讯,即半双工的主从问答式通信,通信波特率为115200bps。

原理可以理解为:舵机控制器作为主机发送读写指令给舵机,舵机作为从机根据具体情况执行相应动作或应答。

1.4 通信协议

(1)指令包格式

帧头ID号数据长度指令参数校验和
0x55 0x55IDLengthCmdPrm 1…Prm NChecksum
  • 帧头:连续收到两个 0x55 ,表示有数据包到达。
  • ID: 每个舵机都有一个 ID 号。ID 号范围 0~253,转换为十六进制 0x00~0xFD。 广播 ID: ID 号254(0xFE) 为广播 ID,若控制器发出的ID号为 254(0xFE),所有的舵机均接收指令,但都不返回应答信息,(读取舵机 ID 号除外,具体说明参见下面指令介绍)以防总线冲突。
  • 数据长度:等于待发送的数据(包含本身一个字节)长度,即数据长度Length加3等于这一包指令的长度,从帧头到校验和。
  • 指令:控制舵机的各种指令,如位置、速度控制等。
  • 参数:除指令外需要补充的控制信息。
  • 校验和:校验和 Checksum,计算方法如下:Checksum = ~ (ID + Length + Cmd+ Prm1 + … Prm N)若括号内的计算和超出 255, 则取最低的一个字节,“~”表示取反。

(2)指令类型

指令有两种,写指令和读指令。

  • 写指令:后面一般带有参数,将相应功能的参数写进舵机,来完成某种动作。
  • 读指令:后面一般不带参数,舵机接收到读指令后会立即返回相应数据,返回的指令值和发送给舵机的“读指令”值相同,并且带有参数。所以上位机发送读指令后要立马准备将自己变为读取状态。

2 程序编写

2.1 cube mx设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zorh0bb7-1684223384005)(assets/image-20230516151529723.png)]

(1)USART1设置

  • 串口1模式配置为同步通讯,波特率改为115200bps,数据宽度8位,检验None,停止位1位
    在这里插入图片描述

  • 增加DMA发送通道
    在这里插入图片描述

  • 打开串口1中断设置
    在这里插入图片描述

(2)USART3设置

  • 串口3模式配置为同步通讯,波特率改为115200bps,数据宽度8位,检验None,停止位1位
    在这里插入图片描述

  • 由于默认串口3引脚是PB10和PB11,而扩展版原理图钟串口3连接的是PC10和PC11,所以串口需要重映射。

    先点击PC11引脚,然后选择USART3_RX,这样操作后,串口3的引脚就会被重映射为PC10和PC11了。
    在这里插入图片描述

  • 打开串口3中断设置

在这里插入图片描述


2.2 程序编写

(1)bsp_key.c

#include "bsp_key.h"
#include "bsp.h"


// 判断按键是否被按下,按下返回KEY_PRESS,松开返回KEY_RELEASE
// Determine if the key is pressed, press to return KEY_PRESS, release to return KEY_RELEASE  
static uint8_t Key1_is_Press(void)
{
	if (!HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
	{
		return KEY_PRESS; // 如果按键被按下,则返回KEY_PRESS
	}
	return KEY_RELEASE;   // 如果按键是松开状态,则返回KEY_RELEASE
}


// 读取按键K1的状态,按下返回KEY_PRESS,松开返回KEY_RELEASE. 
// mode:设置模式,0:按下一直返回KEY_PRESS;1:按下只返回一次KEY_PRESS
// Read the state of key K1, press down to return KEY_PRESS, release to return key_release. 
// mode: setting mode, 0: press down to return KEY_PRESS;  1: KEY_PRESS is returned only once  
uint8_t Key1_State(uint8_t mode)
{
	static uint16_t key1_state = 0;

	if (Key1_is_Press() == KEY_PRESS)
	{
		if (key1_state < (mode + 1) * 2)
		{
			key1_state++;
		}
	}
	else
	{
		key1_state = 0;
	}
	if (key1_state == 2)
	{
		return KEY_PRESS;
	}
	return KEY_RELEASE;
}


/*********************************************END OF FILE**********************/

(2)bsp_key.h

#ifndef __BSP_KEY_H__
#define __BSP_KEY_H__

#include "gpio.h"


#define KEY_PRESS           1
#define KEY_RELEASE         0

#define KEY_MODE_ONE_TIME   1
#define KEY_MODE_ALWAYS     0


uint8_t Key1_State(uint8_t mode);


#endif /* __BSP_KEY_H__ */

(3)bsp_uart.c

/*
 * bsp_uart.c
 *
 *  Created on: Mar 4, 2022
 *      Author: Administrator
 */

#include "bsp_uart.h"
#include "bsp.h"

#define ENABLE_UART_DMA    1

uint8_t RxTemp = 0;
uint8_t RxTemp_3 = 0;
uint8_t UART_RX_BUF[16];
bool isUartRxCompleted= false;

// Initialize USART1  初始化串口1
void USART1_Init(void)
{
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxTemp, 1);
}

// The serial port sends one byte  串口发送一个字节
void USART1_Send_U8(uint8_t ch)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
}

// The serial port sends a string of data  串口发送一串数据
void USART1_Send_ArrayU8(uint8_t *BufferPtr, uint16_t Length)
{
    #if ENABLE_UART_DMA
    HAL_UART_Transmit_DMA(&huart1, BufferPtr, Length);
    #else
    while (Length--)
    {
        USART1_Send_U8(*BufferPtr);
        BufferPtr++;
    }
    #endif
}

// Initialize USART3  初始化串口3
void USART3_Init(void)
{
    HAL_UART_Receive_IT(&huart3, (uint8_t *)&RxTemp, 1);
}

// The serial port sends one byte  串口发送一个字节
void USART3_Send_U8(uint8_t ch)
{
    HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
}

// The serial port sends a string of data  串口发送一串数据
void USART3_Send_ArrayU8(uint8_t *BufferPtr, uint16_t Length)
{
    while (Length--)
    {
        USART3_Send_U8(*BufferPtr);
        BufferPtr++;
    }
}

/**
 *    @brief 通过UART发送一个数据缓冲区中的数据。使用一个循环逐个发送数据字节。
 *    @param
 *
 *    uint8_t *buf:	是指向数据缓冲区的指针,其中包含要发送的数据。
 *    uint8_t len:	是要发送的数据长度
 *    while (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_TXE) == RESET):	这是一个等待循环,
 *            	这是一个等待循环,它会检查UART外设的发送缓冲区是否为空。
 *             	循环将一直执行,直到发送缓冲区为空,
 *              表示前一个字节已经发送完毕,可以发送下一个字节。
 *    HAL_UART_Transmit(&huart3, buf, 1, HAL_MAX_DELAY):
 *    			这是使用HAL库的UART发送函数。
 *    			它将一个字节的数据从buf发送到UART外设。
 *    			第三个参数1表示发送一个字节,
 *    			HAL_MAX_DELAY 表示在发送完成之前不会超时。
 *	  buf++:这将指针buf向后移动一个字节,以指向下一个要发送的字节。
 *
 *    @retval None
 */

void uartWriteBuf(uint8_t *buf, uint8_t len)
{
    while (len--)
    {
        while (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_TXE) == RESET);
        HAL_UART_Transmit(&huart3, buf, 1, HAL_MAX_DELAY);
        buf++;
    }
}


// The serial port receiving is interrupted. Procedure  串口接收完成中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart==&huart1)
    {
        // 测试发送数据,实际应用中不应该在中断中发送数据
        // Test sending data. In practice, data should not be sent during interrupts  
        USART1_Send_U8(RxTemp);

        // Continue receiving data  继续接收数据
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxTemp, 1);
    }
    if (huart==&huart3)
    {

        static bool isGotFrameHeader = false;	//静态变量是不是帧头
        static uint8_t frameHeaderCount = 0;	//帧头计数
        static uint8_t dataLength = 2;			//数据长度2
        static uint8_t dataCount = 0;			//数据计数
        uint8_t Res;			// 假设 UART_RX_BUF 是接收缓冲区
        //读取USART3外设的接收数据寄存器(DR)中的数据。
        Res = (uint8_t)(huart3.Instance->DR & (uint8_t)0xFF);

		if (!isGotFrameHeader)
        {
            if (Res == 0x55)
            {
                frameHeaderCount++;
                if (frameHeaderCount == 2)
                {
                    frameHeaderCount = 0;
                    isGotFrameHeader = true;
                    dataCount = 1;
                }
            }
            else
            {
                isGotFrameHeader = false;
                dataCount = 0;
                frameHeaderCount = 0;
            }
        }
        if (isGotFrameHeader)
        {
            UART_RX_BUF[dataCount] = Res; //将接收到的数据存储到接收缓冲区UART_RX_BUF的相应位置
            if (dataCount == 3)
            {
                dataLength = UART_RX_BUF[dataCount];
                /*如果数据长度小于 3 或大于 7,表示数据长度异常,
                将数据长度恢复为默认值 3,并将标志变量 isGotFrameHeader 设置为 false,
                表示需要重新接收帧头。
                */
                if (dataLength < 3 || dataLength > 7)
                {
                    dataLength = 3;
                    isGotFrameHeader = false;
                }
            }
            dataCount++;
            /*
             * 如果数据计数器 dataCount 的值等于
             * 数据长度加上帧头和数据长度字段的长度(即数据计数达到了预期的总长度),
             * 则表示接收完成。
             */
            if (dataCount == dataLength + 3)
            {
                /*
                 * 如果 isUartRxCompleted 的值为false,表示之前的接收未完成,
                 * 将其设置为true,表示接收完成。
                 */
            	if (isUartRxCompleted == false)
                {
                    isUartRxCompleted = true;
                    //使用 memcpy 函数将接收到的数据复制到目标缓冲区LobotRxBuf,复制的长度为数据计数加上2(帧头和数据长度字段的长度)
                    memcpy(LobotRxBuf, UART_RX_BUF, dataCount + 2);
                }
                //将标志变量 isGotFrameHeader 设置为false,表示需要重新接收帧头。
            	isGotFrameHeader = false;
            }
        }
        /*
         * 启动下一次接收,使用 HAL_UART_Receive_IT 函数以中断方式接收一个字节的数据,
         * 将其存储到Res变量中。这样可以实现连续接收数据的功能。
         */
    	HAL_UART_Receive_IT(&huart3, &Res, 1);
    }
}



bool isRxCompleted(void)
{
	if(isUartRxCompleted == true){
		isUartRxCompleted = false;
		return true;
	}else{
		return false;
	}
}



#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
    /* Place your implementation of fputc here */
    /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return ch;
}

(4)bsp_uart.h

/*
 * bsp_uart.h
 *
 *  Created on: Mar 4, 2022
 *      Author: Administrator
 */

#ifndef BSP_UART_H_
#define BSP_UART_H_

#include "stdint.h"
#include "string.h"
#include "bsp_bool.h"




void USART1_Init(void);
void USART1_Send_U8(uint8_t ch);
void USART1_Send_ArrayU8(uint8_t *BufferPtr, uint16_t Length);


void USART3_Init(void);
void USART3_Send_U8(uint8_t ch);
void USART3_Send_ArrayU8(uint8_t *BufferPtr, uint16_t Length);
void uartWriteBuf(uint8_t *buf,uint8_t len);
bool isRxCompleted(void);


#endif /* BSP_UART_H_ */

(5)bsp_bool.h

/*
 * bsp_bool.h
 *
 *  Created on: May 11, 2023
 *      Author: 77454
 */

#ifndef BSP_BOOL_H_
#define BSP_BOOL_H_

typedef enum{
	false =0,true=!false
}bool;

#endif /* BSP_BOOL_H_ */

(6)bsp_serial_servo.c

/*
 * bsp_serial_servo.c
 *
 *  Created on: May 9, 2023
 *      Author: 77454
 */

#include "bsp_serial_servo.h"
#include "bsp.h"


#define LobotSerialWrite  uartWriteBuf

#define GET_LOW_BYTE(A) ((uint8_t)(A))
//宏函数 获得A的低八位
#define GET_HIGH_BYTE(A) ((uint8_t)((A) >> 8))
//宏函数 获得A的高八位
#define BYTE_TO_HW(A, B) ((((uint16_t)(A)) << 8) | (uint8_t)(B))
//宏函数 将高低八位合成为十六位

uint8_t LobotRxBuf[16];

/**
 *    @brief 计算校验和函数。checksum=~(ID+Length+Cmd+Prm1+...+PrmN)
 *    @param None
 *    @retval None
 */
uint8_t LobotCheckSum(uint8_t buf[])
{
  uint8_t i;
  uint16_t temp = 0;
  for (i = 2; i < buf[3] + 2; i++) {
    temp += buf[i];
  }
  temp = ~temp;
  i = (uint8_t)temp;
  return i;
}

/**
 *    @brief 设置舵机ID,并且掉电保存
 *    @param
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:将数组的第三个元素设置为oldID,即当前舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为4。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用设置舵机ID的指令,指令名为LOBOT_SERVO_ID_WRITE。指令值为13
 *    buf[5]		:将数组的第六个元素设置为newID,即要设置的新舵机ID
 *    buf[6]		:将数组的第七个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval None
 */
void LobotSerialServoSetID(uint8_t oldID, uint8_t newID)
{
  uint8_t buf[7];//定义了一个长度为7的uint8_t类型数组buf,用于存储发送给舵机的指令数据。
  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = oldID;//
  buf[3] = 4;
  buf[4] = LOBOT_SERVO_ID_WRITE;
  buf[5] = newID;
  buf[6] = LobotCheckSum(buf);
  //USART3_Send_ArrayU8(buf,sizeof(buf));
  LobotSerialWrite(buf, 7);
}
/**
 *    @brief 舵机转动指令
 *    @param
 *    检查舵机位置 position 的取值范围,如果小于0则将其设为0,如果大于1000则将其设为1000,确保位置值在合法范围内。
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:控制舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为7。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用设置舵机ID的指令,指令名为LOBOT_SERVO_MOVE_TIME_WRITE。指令值为1
 *    buf[5]		:角度低八位
 *    buf[6]		:角度高八位0-1000,对应舵机角度为0-240°
 *    buf[7]		:时间低八位
 *    buf[8]		:时间高八位0-30000ms
 *    buf[9]		:将数组的第十个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval None
 */
void LobotSerialServoMove(uint8_t id, int16_t position, uint16_t time)
{
  uint8_t buf[10];
  if(position < 0)
    position = 0;
  if(position > 1000)
	position = 1000;
  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = id;
  buf[3] = 7;
  buf[4] = LOBOT_SERVO_MOVE_TIME_WRITE;
  buf[5] = GET_LOW_BYTE(position);
  buf[6] = GET_HIGH_BYTE(position);
  buf[7] = GET_LOW_BYTE(time);
  buf[8] = GET_HIGH_BYTE(time);
  buf[9] = LobotCheckSum(buf);
  //USART3_Send_ArrayU8(buf,sizeof(buf));
  LobotSerialWrite(buf, 10);
}

/**
 *    @brief 装载电机,有力矩输出,参数为1
 *    @param
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:将数组的第三个元素设置为ID,即当前舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为4。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用加载或卸载写的指令,指令名为LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE。指令值为31
 *    buf[5]		:参数为1,则装载电机,有力矩输出
 *    buf[6]		:将数组的第七个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval None
 */
void LobotSerialServoLoad(uint8_t id)
{
  uint8_t buf[7];
  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = id;
  buf[3] = 4;
  buf[4] = LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE;
  buf[5] = 1;
  buf[6] = LobotCheckSum(buf);
 // USART3_Send_ArrayU8(buf,sizeof(buf));

  LobotSerialWrite(buf,7);
}

/**
 *    @brief 卸载电机,无力矩输出,参数为1
 *    @param
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:将数组的第三个元素设置为ID,即当前舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为4。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用加载或卸载写的指令,指令名为LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE。指令值为31
 *    buf[5]		:参数为0,则卸载电机,无力矩输出
 *    buf[6]		:将数组的第七个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval None
 */
void LobotSerialServoUnload(uint8_t id)
{
  uint8_t buf[7];
  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = id;
  buf[3] = 4;
  buf[4] = LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE;
  buf[5] = 0;
  buf[6] = LobotCheckSum(buf);
  LobotSerialWrite(buf, 7);
}
/**
 *    @brief 读取舵机的温度
 *    @param
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:将数组的第三个元素设置为ID,即当前舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为3。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用加载或卸载写的指令,指令名为 LOBOT_SERVO_POS_READ。指令值为28
 *    buf[5]		:将数组的第六个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval 定义一个名为temp的变量,用于存储函数的返回值,即读取到的温度。
 */
int LobotSerialServoReadTemperature(uint8_t id)
{
  int temp;
  uint8_t buf[6];

  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = id;
  buf[3] = 3;
  buf[4] = LOBOT_SERVO_TEMP_READ;
  buf[5] = LobotCheckSum(buf);

  LobotSerialWrite(buf, 6);
  temp = LobotSerialMsgHandle();

  return temp;
}


/**
 *    @brief 读取舵机的角度位置
 *    @param
 *    buf[0]、buf[1]	:将数组的第一个和第二个元素都设置为0x55,表示有数据包到达;
 *    buf[2]		:将数组的第三个元素设置为ID,即当前舵机的ID;
 *    buf[3]		:将数组的第四个元素设置为3。表示数据帧的长度,指示了要发送的指令的字节数。
 *    buf[4]		:调用加载或卸载写的指令,指令名为 LOBOT_SERVO_POS_READ。指令值为28
 *    buf[5]		:将数组的第六个元素设置为通过调用LobotCheckSum函数计算得到的校验和。
 *    @retval 定义一个名为ret的变量,用于存储函数的返回值,即读取到的位置信息。
 */
int LobotSerialServoReadPosition(uint8_t id)
{
  int ret;
  uint8_t buf[6];

  buf[0] = buf[1] = LOBOT_SERVO_FRAME_HEADER;
  buf[2] = id;
  buf[3] = 3;
  buf[4] = LOBOT_SERVO_POS_READ;
  buf[5] = LobotCheckSum(buf);

  LobotSerialWrite(buf, 6);
  //调用 LobotSerialMsgHandle函数,处理接收到的舵机返回数据,并将处理结果赋值给ret变量
  ret = LobotSerialMsgHandle();

  return ret;
}
/**
 *    @brief 处理串口消息的函数
 *    @param
 *
 *    @retval 定义一个名为ret的变量,用于存储函数的返回值,即读取到的位置信息。
 */
int LobotSerialMsgHandle(void)
{
	int count = 50000;	//该变量用于循环计数,限制处理时间以避免无限循环。
	uint8_t cmd;		//用于存储接收到的指令类型
	int ret;			//用于存储函数的返回值。
	int temp;
	/*
	使用循环判断串口接收是否完成,如果接收未完成,则继续循环等待。
	如果count变量小于0,表示超过了预定的等待时间,返回一个特定的错误码-2048。
	*/
	while(!isRxCompleted())
	{
		count--;
		if(count < 0)
			return -2048;
	}
	/*
	校验接收到的数据帧的校验和是否正确
	通过调用 LobotCheckSum函数计算接收缓冲区LobotRxBuf中数据的校验和,
	然后将其与数据帧中的校验和进行比较。如果校验和不匹配,返回一个特定的错误码-2049。
	*/
	if(LobotCheckSum(LobotRxBuf) != LobotRxBuf[LobotRxBuf[3]+2])
	{
		return -2049;
	}

	cmd = LobotRxBuf[4];//将接收到的数据帧中的指令类型存储在cmd变量中。
	/*
	将接收到的舵机位置数据转换为一个int类型的值,并存储在 ret 变量中。
	这里使用了一个BYTE_TO_HW宏来将两个字节合并成一个 16 位的值。
	*/
	switch(cmd)
	{
		case LOBOT_SERVO_POS_READ:
			ret = (int)BYTE_TO_HW(LobotRxBuf[6], LobotRxBuf[5]);
			return ret;	//将变量 ret 作为函数的返回值返回,表示读取到的舵机位置。
		case LOBOT_SERVO_TEMP_READ:
			temp = LobotRxBuf[5];
			return temp;
		default:
			break;
	}
	return 0;	//如果指令类型不匹配或者没有特定的处理操作,返回一个默认的值 0。
}

(7)bsp_serial_servo.h

/*
 * bsp_serial_servo.h
 *
 *  Created on: May 9, 2023
 *      Author: 77454
 */

#ifndef BSP_SERIAL_SERVO_H_
#define BSP_SERIAL_SERVO_H_

#include "stdint.h"

#define LOBOT_SERVO_FRAME_HEADER         0x55 	//帧头
#define LOBOT_SERVO_MOVE_TIME_WRITE      1		//移动时间写入
#define LOBOT_SERVO_MOVE_TIME_READ       2		//移动时间读取
#define LOBOT_SERVO_MOVE_TIME_WAIT_WRITE 7		//移动时间等待写
#define LOBOT_SERVO_MOVE_TIME_WAIT_READ  8		//移动时间等待读
#define LOBOT_SERVO_MOVE_START           11		//移动开始
#define LOBOT_SERVO_MOVE_STOP            12		//移动停止
#define LOBOT_SERVO_ID_WRITE             13		//舵机ID写
#define LOBOT_SERVO_ID_READ              14		//舵机ID读
#define LOBOT_SERVO_ANGLE_OFFSET_ADJUST  17		//角度偏移调整
#define LOBOT_SERVO_ANGLE_OFFSET_WRITE   18		//角度偏移写
#define LOBOT_SERVO_ANGLE_OFFSET_READ    19		//角度偏移读
#define LOBOT_SERVO_ANGLE_LIMIT_WRITE    20		//角度限制写
#define LOBOT_SERVO_ANGLE_LIMIT_READ     21		//角度限制读
#define LOBOT_SERVO_VIN_LIMIT_WRITE      22		//VIN限制写
#define LOBOT_SERVO_VIN_LIMIT_READ       23		//VIN限制读
#define LOBOT_SERVO_TEMP_MAX_LIMIT_WRITE 24		//温度最大限度写
#define LOBOT_SERVO_TEMP_MAX_LIMIT_READ  25		//温度最大限度读
#define LOBOT_SERVO_TEMP_READ            26		//温度读
#define LOBOT_SERVO_VIN_READ             27		//VIN电压读
#define LOBOT_SERVO_POS_READ             28		//POS位置读
#define LOBOT_SERVO_OR_MOTOR_MODE_WRITE  29		//模式写
#define LOBOT_SERVO_OR_MOTOR_MODE_READ   30		//模式读
#define LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE 31		//加载或卸载写
#define LOBOT_SERVO_LOAD_OR_UNLOAD_READ  32		//加载或卸载读
#define LOBOT_SERVO_LED_CTRL_WRITE       33		//LED控制写
#define LOBOT_SERVO_LED_CTRL_READ        34		//LED控制读
#define LOBOT_SERVO_LED_ERROR_WRITE      35		//LED错误写
#define LOBOT_SERVO_LED_ERROR_READ       36		//LED错误读

#define LOBOT_DEBUG 1

extern uint8_t LobotRxBuf[16];

uint8_t LobotCheckSum(uint8_t buf[]);

void LobotSerialServoSetID(uint8_t oldID, uint8_t newID);
void LobotSerialServoMove(uint8_t id, int16_t position, uint16_t time);
void LobotSerialServoLoad(uint8_t id);
void LobotSerialServoUnload(uint8_t id);
int LobotSerialServoReadTemperature(uint8_t id);
int LobotSerialServoReadPosition(uint8_t id);
int LobotSerialMsgHandle(void);

#endif /* BSP_SERIAL_SERVO_H_ */

(8)bsp.c

#include "bsp.h"


// The peripheral device is initialized  外设设备初始化
void Bsp_Init(void)
{

	USART1_Init();
	USART3_Init();
}



// main.c中循环调用此函数,避免多次修改main.c文件。
// This function is called in a loop in main.c to avoid multiple modifications to the main.c file
void Bsp_Loop(void)
{
	 //Detect button down events   检测按键按下事件
	if (Key1_State(KEY_MODE_ONE_TIME))
	{
		Beep_On_Time(50);
		static int press = 0;
		int temp;
		press++;
		printf("press:%d\n", press);

//		UartServo_Get_Angle(servo_id);
		HAL_Delay(12);
		if (press%2)
		{
			LobotSerialServoMove(1, 500, 500);
			LobotSerialServoMove(2, 500, 500);
			temp=LobotSerialServoReadTemperature(1);
			printf("temp :%d\n", temp);

		}
		else
		{
			LobotSerialServoMove(1, 150, 500);
			LobotSerialServoMove(2, 150, 500);
			temp=LobotSerialServoReadTemperature(1);
			printf("temp :%d\n", temp);


		}
	}


	HAL_Delay(10);
}

(9)bsp.h

#ifndef __BSP_H__
#define __BSP_H__

/* Import HAL related library  导入HAL相关库 */
#include "main.h"
#include "gpio.h"
#include "usart.h"

#include "stm32f1xx_hal.h"
#include "stm32f103xe.h"


/* Import device driver library  导入设备驱动库 */

#include "bsp_key.h"
#include "bsp_uart.h"

#include "bsp_serial_servo.h"


#include "stdio.h"



/* functions */
void Bsp_Init(void);
void Bsp_Loop(void);


#endif /* __BSP_H__ */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/532831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C程序设计】——最简单的C语言程序

目录 &#x1f34a;&#x1f34a;一、最简单的C语言程序 1.1 最简单的C语言程序举例 1.2 C语言程序的结构 首先&#xff0c;让我们先了解一下C语言的特点&#xff1a; 语言简洁、紧凑&#xff0c;使用方便、灵活&#xff1b;运算符丰富&#xff1b;数据类型丰富&#xff1b…

806. 写字符串需要的行数

806. 写字符串需要的行数 一、题目描述&#xff1a; 我们要把给定的字符串 S 从左到右写到每一行上&#xff0c;每一行的最大宽度为100个单位&#xff0c;如果我们在写某个字母的时候会使这行超过了100 个单位&#xff0c;那么我们应该把这个字母写到下一行。我们给定了一个数…

关于ubuntu20.04 apt 安装源中搜索不到最新版本gcc 12的问题

一、问题描述 最近在搞Open 3d 点云point cloud 相关的东西&#xff0c;过程需要安装较高版本的cmake 3.20版本以上&#xff0c;3.20版本又需要gcc 更高版本 至少11.0以上&#xff0c;理论上本机配置的有 ubuntu 官方的源和阿里云的源&#xff0c;不过 通过搜索就只能搜索安装的…

大模型中的temperature参数+随机采样策略

一、问题来源&#xff1a; 使用GPT-3.5的时候发现相同的输入会得不一样的结果 二、根因定位&#xff1a; 核心就在于采样策略&#xff0c;一图胜千言&#xff1a; 上图中语言模型 (language model) 的预测输出其实是字典中所有词的概率分布&#xff0c;而通常会选择生成其中…

【JavaScript全解析】ES6定义变量与箭头函数详解

箭头函数可以说是ES6的一大亮点,使用箭头函数,可以简化编码过程,使代码更加的简洁 本文由千锋前端老师独家创作&#xff0c;主要给大家介绍了关于ES6中箭头函数的相关资料,文中通过实例代码介绍的非常详细,觉得有帮助的话可以【关注】持续追更~ ES6定义变量 我们现在知道定义…

二、easyUI中的layout(布局)组件

1.layout&#xff08;布局&#xff09;组件的概述 布局容器有5个区域&#xff1a;北、南、东、西和中间。中间区域面板是必须的&#xff0c;边缘的面板都是可选的。每个边缘区域面板都可以通过拖拽其边框改变大小&#xff0c;也可以点击折叠按钮将面板折叠起来。布局可以进行嵌…

前端单元测试是怎么做的?

为什么要做单元测试 1. 执行单元测试&#xff0c;就是为了证明这段代码的行为和我们期望的一致 2. 进行充分的单元测试&#xff0c;是提高软件质量&#xff0c;降低开发成本的必由之路 3. 在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用 怎么去…

YOLOv8中的C2f的详细解读

C2f的结构图,看不懂没关系,继续往下看,一定会看懂的!!!首先是C2f的逻辑代码: class C2f(nn.Module):# CSP Bottleneck with 2 convolutionsdef __init__(self, c1, c2, n=1, shortcut=

排序算法的比较与java实现

冒泡排序 基本思想: 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。 对每一对相邻元素作同样的工作&#xff0c;从开始第一对到结尾的最后一对。在这一点&#xff0c;最后的元素应该会是最大的数。 针对所有的元素重复以上的步骤&#xff0c;除了最后一个。…

网络安全大厂常见面试题

以下为网络安全各个方向涉及的面试题&#xff0c;星数越多代表问题出现的几率越大&#xff0c;祝各位都能找到满意的工作。 注&#xff1a;本套面试题&#xff0c;已整理成pdf文档&#xff0c;但内容还在持续更新中&#xff0c;因为无论如何都不可能覆盖所有的面试问题&#xf…

原神3.2服务端架设服务器搭建教程ubuntu系统(保姆级)

原神3.2服务端架设服务器搭建教程ubuntu系统&#xff08;保姆级&#xff09; 大家好&#xff0c;我是艾西今天跟大家分享下原神3.2服务端架设ubuntu系统实操教程 准备阶段&#xff1a;服务器一台 32h32g起、服务端、客户端、服务器装Ubuntu20.04系统 特别强调&#xff1a;ja…

怎样通过font属性控制CSS字体样式?

为了更方便地控制网页中各种各样的字体&#xff0c;CSS提供了一系列的字体样式属性&#xff0c;具体如下。 (1)font-size属性&#xff1a;字号 font-size属性用于设置字号&#xff0c;该属性的属性值可以为像素值、百分比数值、倍率等。表3-l列举了fomt-size属性常用的属性值…

回炉重造十四---微服务

微服务 1、zookeeper 1.1Zookeeper的功能 1.1.1命名服务 命名服务是分布式系统最基本的公共服务之一。在分布式系统中&#xff0c;被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等一一这些我们都可以通称他们为名字&#xff08;Name)&#xff0c;通过命名…

SpringCloud_服务调用_Ribbon概述以及使用(一)

SpringCloud_负载均衡_Ribbon(一) 概述 Ribbbon负载均衡演示 Ribbbon核心组件IRule Ribbbon负载均衡算法 概述 Ribbbon是一套客户端 负载均衡的工具 提供客户端的软件负载均衡算法和服务调用 地址&#xff1a; https://github.com/Netflix/ribbon/wiki/Getting-Started 目前这几…

java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver报错问题分析

java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver 这个错误通常意味着应用程序服务器在尝试在只读模式下访问数据库时出现了问题&#xff0c;该错误与事务处理有关。通常出现在JDBC连接对象或事务对象的创建或状态查询的过程中。 在JDBC中&#xff0c;只读事务需要…

【机器视觉2】单目相机内外参数标定

单目相机内外参数标定 1. 标定参照物概述2. 张正友平面模板标定法 1. 标定参照物概述 标定相机内外参数需要获取场景和图像间多个坐标对。场景坐标点直接从标定参照物获取&#xff0c;图像坐标点从图像中获取。 标定参照物有二维、三维等&#xff0c;如下图所示&#xff1a; …

【linux】冯诺依曼体系+操作系统

我们使用的计算机都是由一个个硬件所组成的&#xff0c;那么如何有条不紊的运行呢&#xff1f;那是因为有冯诺依曼体系约束着硬件&#xff0c;而操作系统来管理着他们&#xff0c;从而使得计算机的硬件和软件完美结合。 一、冯诺依曼体系 首先我们得了解什么是冯诺依曼体系结构…

万字长文 | ChatGPT的工作原理(一)

ChatGPT 能够自动生成一些读起来表面上甚至像人写的文字的东西&#xff0c;这非常了不起&#xff0c;而且出乎意料。但它是如何做到的&#xff1f;为什么它能发挥作用&#xff1f;我在这里的目的是大致介绍一下 ChatGPT 内部的情况&#xff0c;然后探讨一下为什么它能很好地生成…

Sharding-JDBC之绑定表(关联表)

目录 一、简介二、maven依赖三、数据库3.1、创建数据库3.2、创建表 四、配置&#xff08;二选一&#xff09;4.1、properties配置4.2、yml配置 五、实现5.1、实体层5.2、持久层5.3、服务层5.4、测试类5.4.1、保存订单数据5.4.2、查询订单详情数据&#xff08;关联表&#xff09…

拓扑排序在处理树形关系结构中的应用

Preface 偶然在QQ上的一个交流群中看到了一位群友的棘手需求。互联网开发中&#xff0c;数据的落盘存储通常在MySQL中。MySQL是一种关系型数据库&#xff0c;以“行”为基本的存储单元&#xff0c;然后通过外键等建立数据实体模型之间的联系。 但有些数据的存储&#xff0c;在…