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

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主控

STM32HAL库 总线舵机驱动库的编写_第1张图片
STM32HAL库 总线舵机驱动库的编写_第2张图片

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

1.2 电路图

STM32HAL库 总线舵机驱动库的编写_第3张图片

1.3 原理

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

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

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

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

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

1.4 通信协议

(1)指令包格式

帧头 ID号 数据长度 指令 参数 校验和
0x55 0x55 ID Length Cmd Prm 1…Prm N Checksum
  • 帧头:连续收到两个 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设置

STM32HAL库 总线舵机驱动库的编写_第4张图片

(1)USART1设置

  • 串口1模式配置为同步通讯,波特率改为115200bps,数据宽度8位,检验None,停止位1位
    STM32HAL库 总线舵机驱动库的编写_第5张图片

  • 增加DMA发送通道
    STM32HAL库 总线舵机驱动库的编写_第6张图片

  • 打开串口1中断设置
    STM32HAL库 总线舵机驱动库的编写_第7张图片

(2)USART3设置

  • 串口3模式配置为同步通讯,波特率改为115200bps,数据宽度8位,检验None,停止位1位
    STM32HAL库 总线舵机驱动库的编写_第8张图片

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

    先点击PC11引脚,然后选择USART3_RX,这样操作后,串口3的引脚就会被重映射为PC10和PC11了。
    STM32HAL库 总线舵机驱动库的编写_第9张图片

  • 打开串口3中断设置

STM32HAL库 总线舵机驱动库的编写_第10张图片


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__ */

你可能感兴趣的:(STM32HAL库入门学习,stm32,单片机,嵌入式硬件)