STM32机器人控制开发教程No.4 使用串口通信控制电机(基于HAL库)

在机器人控制中,单片机(Arduino/STM32)与上位机(Raspberry Pi/NVIDIA Jetson nano)之间的通信经常采用串口通信的方式,那应该如何使用STM32的串口通信以及根据自己定义的协议来完成数据的接收与发送呢?在本篇文章中将给你演示如何通过自定协议来完成对电机的控制以及获取编码器的值,跟着我们的配置步骤,你会发现一切如此的简单!

本篇文章依旧采用我们的机器人控制板进行开发,关于电机的相关配置以及驱动代码可以参考前面的文章,本文着重介绍串口通信部分!

1 确定串口的数据协议

'e' 反馈两个电机的编码器脉冲计数值,该计数值达到最大值或最小值时自动清零。

'm' l_speed r_speed 'm'为控制标志,l_speed为左轮的速度值,r_speed为右轮的速度值,该值单位为 cm/s

2 配置串口
 

为了提高串口通信的效率,减少因为字节传输而不断引起中断导致资源的浪费,可以采用DMA+串口空闲中断的方式对数据进行接收。

DMA

DMA:Direct Memory Access,可以实现一个数据从一个地址空间拷贝到另一个地址空间,并且在数据拷贝过程中无需CPU的干预,在数据拷贝结束后才告知CPU进行处理。因此使用DMA功能可以释放CPU资源。

串口空闲中断

普通的串口处理数据方式为单字节接收,并且接收一帧数据时,需要自行判断帧头帧尾确定是否为一帧完整数据,并且当数据量大会导致频繁进入中断。而采用串口空闲中断,在串口空闲时(发送完一帧数据)产生中断,并且可以在中断服务函数中计算得到的数据长度,对整帧数据进行处理。

STM32CubeMX中的配置

在串口1配置中打开DMA功能,其余配置可参考之前的工程

3 编写驱动代码

创建好工程后,打开工程文件中的stm32f1xx_it.c,找到串口1的中断服务函数,在中断服务函数中加入以下代码。

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  uint32_t tmp_flag = 0;
    uint32_t temp;
    tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
    if((tmp_flag != RESET))//idle标志被置位
    { 
      __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
      HAL_UART_DMAStop(&huart1); 
      temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数   
​
      rx_len =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
      recv_end_flag = 1;  // 接受完成标志位置1  
    }
  HAL_UART_Receive_DMA(&huart1,uart1_rx_buffer,BUFFER_SIZE);//重新打开DMA接收
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
​
  /* USER CODE END USART1_IRQn 1 */
}

并且在main函数中的主循环开始前加入使能语句

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1,uart1_rx_buffer,BUFFER_SIZE);

主循环中的代码如下:

while (1)
  {
    if(recv_end_flag == 1) //串口中断接收标志位
    {
      if(uart1_rx_buffer[0] == 0x6d)  //若接收到'm'
      {
        if(rx_len <=9)  
        {
          leftTargetSpeed = atof(uart1_rx_buffer+1);  //将接收到的字符转换为十进制数
          rightTargetSpeed = atof(uart1_rx_buffer+4);
           if(uart1_rx_buffer[2] == 0x2d)
          {
            leftTargetSpeed = atof(uart1_rx_buffer+1);  //将接收的字符串转换为float类型
            rightTargetSpeed = atof(uart1_rx_buffer+5);
          }
        }
        else if(rx_len > 9)  
        {
          leftTargetSpeed = atof(uart1_rx_buffer+1);
          rightTargetSpeed = atof(uart1_rx_buffer+5);          
        }
      }
      else if(uart1_rx_buffer[0] == 0x65)  //若接收到'e'
      {
        int leftPulse,rightPulse;
        leftPulse = encoderPulse[0];  //获得当前的编码器脉冲值
        rightPulse = encoderPulse[1];
        printf("%d %d\r\n",leftPulse,rightPulse);
      }
      recv_end_flag = 0; //标志位置零等待下一次接收
      rx_len = 0;
      memset(uart1_rx_buffer,0,rx_len); //初始化接收数组
    }
     /* USER CODE END WHILE */
  }

4 测试结果演示

当发送"m 0 0"时,电机停止,并返回"OK"表示已经成功设置两轮速度

当发送"m 10 10"时,左右轮以10cm/s的速度转动

若要向反方向运动,则发送负的速度值,例如发送"m -10 -10"时,左右轮以10cm/s反向转动

当发送"e"时,可以得到当前编码器脉冲计数值。

这样控制板和上层设备之间的基本的串口控制和通信就建立好啦,快去试试效果吧!

猜你喜欢

转载自blog.csdn.net/COONEO/article/details/126470365