1. 项目概述

本项目基于STM32F103C8T6最小系统板,使用HAL库开发,实现以下核心功能:通过DMA接收上位机VOFA+发送的数据,将接收到的数据解析为浮点数,用于驱动舵机旋转到指定角度;同时通过DMA按照JustFloat协议向上位机回传当前角度和PWM占空比数据,实现双向实时通信。

2. 硬件准备

在开始编写代码之前,需要准备以下硬件和软件环境:

  • STM32F103C8T6最小系统板:主控芯片,72MHz主频,64KB Flash,20KB RAM。
  • 舵机:推荐SG90,工作电压5V,PWM控制信号周期20ms。
  • USB转TTL模块:如CH340,用于与上位机通信。
  • VOFA+上位机软件:可以下载开源软件VOFA+进行上位机
  • 烧录器:可以用stlink或者其他的
  • 5V电源:为舵机供电,注意与主控板共地(没有的话,简易版可以用stlink的5V)。

3. JustFloat协议说明

JustFloat协议是VOFA+上位机支持的一种简单浮点数据传输协议,特点是适用于大量浮点数传输,并且实时绘出图像便于调参,VOFA+中简介如下:

可以通过建立一个结构体来存储浮点数数据以及帧尾,帧尾的作用就是让VOFA+能识别到该组数据,每次发送的数据都是数据+帧尾,其中帧尾固定为0x00 0x00 0x80 0x7F

本文介绍代码并不采用结构体,而是通过拷贝浮点数数据到发送包里面,再添加帧尾,可以选择发送的浮点数的个数,并不是固定四个浮点数数据,因为本项目是MCU发送当前角度和占空比,所以需要两个

3. 构思思路

提前做好构思思路,有利于模块化编程,利于后期调试以及方便移植

  1. 初始化,选择PA10PA9即UART1来进行异步通信,使用PA1即TIM2定时器的通道2来产生PWM,可以选择两个引脚来进行OLED显示方便后期调试
  2. 定时器的预分频寄存器(PSC)和自动重装寄存器(ARR)配置,要满足周期是20ms,周期公式是((PSC +1) * (ARR + 1)) / 定时器时钟频率,比如介绍代码就是((72 -1 + 1)*(20000 -1 + 1))/ 72M,72M即72*10的6次方,计算得出周期是20ms,占空比 = CCR / ARR,Sg90舵机,0度对应CCR = 500,90度对应CCR = 1500,180度对应CCR = 2500,函数关系式为CCR = Angle / 180 * 2000 + 500
  3. 编程大体可分为:接受串口发出的的数据 -> 转化为浮点数 -> 计算所需要设置的CCR让舵机旋转 -> 计算出占空比 -> 把两个数据拷贝到发送包里面添加帧尾并发送

3. 开发环境搭建(不会可以去Bi站学习,这一步不难)

使用STM32CubeMX生成初始化代码,配置步骤如下:

  1. 打开STM32CubeMX,选择芯片STM32F103C8T6。
  2. 配置时钟树,使用外部8MHz晶振,系统时钟设置为72MHz。
  3. 配置USART1为异步通信模式,波特率9600,8位数据位,1位停止位,无校验。
  4. 在USART1的DMA设置中,添加接收和发送两个DMA通道,模式均为Normal。
  5. 配置TIM2中通道2为PWM输出模式,通道2用于舵机控制,频率50Hz(周期20ms)。
  6. 生成工程代码,选择MDK-ARM作为工具链。
  7. 优先级配置

5. 代码实现步骤

按照以下步骤编写代码,实现完整功能:

以下是 fun.c 的完整代码内容:

#include "fun.h"

#define MAXCOUNT 4 //最大通道数,根据自己的需求以及VOFA+的最大通道设置

/**
* @brief 设置定时器2通道2的捕获比较寄存器的值CCR(PA2)
* @param 设置的角度
* @retval 无
**/
void SetTIM2_CCR(float angle)
{
uint32_t CCR = (uint32_t)(angle / 180 * 2000 + 500);
TIM2->CCR2 = CCR;
}

/**
* @brief 获取定时器2通道2的占空比
* @retval 返回占空比
**/
float Get_TIM2Duty(void)
{
return (float)TIM2->CCR2 /TIM2->ARR;
}

bool Rx_flag = 0; //接受标志位
uint8_t Rx_str[20] = {0};
uint16_t Rx_length;
uint8_t Tx_str[MAXCOUNT * 5] = {0};
uint16_t Tx_length = 0;

/**
* @brief 串口通过JustFloat协议发送数据
* @param 传输的浮点数数据指针
* @param 指针的值的个数
* @retval 无
* */
void uart_TransmitJustFloatData(float *data,uint8_t count)
{
Tx_length = count * 4;
memcpy(Tx_str,data,Tx_length); //把data的Tx_length字节数据拷贝到Tx_str中,需包含string.h
Tx_str[Tx_length + 0] = 0x00; //进行帧尾处理,满足VOFA+JustFloat协议要求
Tx_str[Tx_length + 1] = 0x00;
Tx_str[Tx_length + 2] = 0x80;
Tx_str[Tx_length + 3] = 0x7F;
Tx_length += 4;

while(huart1.gState != HAL_UART_STATE_READY); //等待串口1是否空闲,空闲退出
HAL_UART_Transmit_DMA(&huart1, Tx_str, Tx_length);
}

/**
* @brief 接受数据的回调函数
* @param 串口句柄
* @param 接受到的数据大小
* @retval 无
* */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
Rx_length = Size;
Rx_flag = 1;
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx_str,20);
}
}

/**
* @brief 将接受到的字符串数据转化为浮点数数据
* @retval 返回角度
**/
float Get_Angle(void)
{
float Angle = 0;
Angle = atof((char*)Rx_str); //自动将字符串数据转化为浮点数数据,需要包含stdlib.h
Angle = Angle < 0 ? 0 : Angle;
Angle = Angle > 180 ? 180 : Angle;

memset(Rx_str,0,20); //清空Rx_str数据,防止数据错位,需包含string.h
return Angle;
}

以下是 fun.h 的完整代码内容:

#ifndef _FUN_H
#define _FUN_H

#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

#include "stdbool.h"
#include "string.h"
#include "stdlib.h"

#include "OLED.h"

void SetTIM2_CCR(float angle);
float Get_TIM2Duty(void);
void uart_TransmitJustFloatData(float *data,uint8_t count);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
float Get_Angle(void);

#endif
 

以下是 main.c 的部分代码内容:

/* USER CODE BEGIN PV */
extern uint8_t Rx_str[20];
extern bool Rx_flag;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_ShowString(1,1,"Ang:");
    OLED_ShowString(2,1,"CCR:");
    
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx_str,20);        //开启空闲DMA接受数据
    //关掉数据传输到一半是产生中断的功能,默认是开的,不关会丢包
    __HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT);          
    
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
    
    float fdata[2] = {0};

    
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        static uint32_t lastTick = 0;
        uint32_t nowTick = HAL_GetTick();
        if (nowTick - lastTick >=500)
        {
            uart_TransmitJustFloatData(fdata,2);
            lastTick = nowTick;
        }
        if (Rx_flag)
        {
            fdata[0] = Get_Angle();
            SetTIM2_CCR(fdata[0]);
            fdata[1] = Get_TIM2Duty();
            Rx_flag = 0;
        }
        OLED_ShowFloat(1,5,fdata[0],3,1);
        OLED_ShowFloat(2,5,fdata[1] * 100,2,1);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

6. VOFA+上位机配置

在VOFA+中完成以下配置,即可实现数据可视化与控制:

  1. 打开VOFA+,选择串口通信模式,设置波特率9600。
  2. 选择JustFloat协议作为数据解析方式。
  3. 连接,并点击控件,拖动波形图,鼠标右键选择填充选择第一个,然后Y轴可以选择通道

7. 调试与注意事项

在实际调试过程中,需要注意以下几个关键问题:

  • 舵机供电:电流太小舵机会滋滋响
  • Rx_str和Tx_str缓冲区要适宜:float为4字节,4个浮点数数据需要4 * 4字节,帧尾一共4字节,所以如果最大通道为4,可设置20字节
  • PWM频率或者周期:舵机控制信号频率必须为50Hz,对应周期20ms。
  • 占空比范围:舵机0度对应0.5ms高电平,180度对应2.5ms高电平,换算为占空比即2.5%到12.5%。
  • 数据丢包:__HAL_DMA_DISABLE_IT(huart1.hdmarx, DMA_IT_HT); 这一句不关闭,数据传输到一半就会产生中断,导致接收数据不全产生丢包现象,HT代表Half Transfer,只要关闭一次即可
  • 启动函数:如果接收不到数据或者OLED显示正常数据,但是舵机没有旋转,可以看看main.c文件里面是否开启了DMA空闲接收数据和PWM使能

8. 代码下载链接:

git中的ServoAndJustFloat代码:https://github.com/LCZ-Xuan/basic-project.git

9. 总结

本项目完整实现了基于STM32F103C8T6的DMA串口通信与舵机控制,通过JustFloat协议与VOFA+上位机进行双向数据交互。主要是学习DMA的使用、上位机能力以及JustFloat协议、舵机知识、串口如何处理数据、PSC、ARR定时器知识等。项目虽然小,但体现基础。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐