本来以为实现起来不难,没想到这么多坑,这里记录一下实现过程。

软硬件资源

  • 硬件

    • STM32F103C8T6 开发板

    • ESP32-S3 开发板

  • 软件

    • ESP-IDF v4.4

    • HAL 库

    • FreeRTOS

连线

ESP32-S3 STM32F103C8T6
TX GPIO_44 PB6
RX GPIO_43 PB7
串口 UART0 USART1
  • ESP32-S3 的 UART0 默认情况下为 log 输出口,想要作为普通串口需要去 manuconfig 作一些修改,如何修改可以看这篇文章:关于ESP32 S3 LOG打印输出开启或关闭的相关选项说明
    • UART0 不能修改引脚,UART1 和 UART2 可以修改到任意引脚,建议使用这两个串口,会方便很多。我是因为画板子的时候就定好了引脚,打好板没法改了
  • STM32F103C8T6 的 USART1 默认端口不是 PB6/7,需要重映射,如何重映射看下面的这篇文章
  • 接线时,TX 连 RX,RX 连 TX

代码实现

STM32 部分

发送

这里我直接用的 printf(已重定向)。

项目中,STM32 向 ESP32 发送的数据是不同含义、不定长的数据,其中首字节为数据类型,后续字节为具体数据,且需要保留小数点后两位,因此需要用 sprintf 先格式化,然后再发送。

以下为具体代码:

void Uart_SendData_float(char data_type,float data){
  char buffer[20];
  sprintf(buffer,"%c%.2f",data_type,data);//格式化:拼接数据+浮点数保留两位
  printf("%s\n",buffer);//换行符\n不能去掉,否则ESP32接收会有问题,原因未知
}

void Uart_SendData_int(char data_type,int data){
  char buffer[20];
  sprintf(buffer,"%c%d",data_type,data);
  printf("%s\n",buffer);
}

static void TX_Task(void){
  vTaskDelay(1000);//上电后延时,等待ESP32接收端准备好之后开始发送数据
  static uint8_t i = 0;
  while(1){
    //轮流发送不同类型的数据
    switch(i){
      case 0:
        Uart_SendData_float(DATA_TYPE_ACC,ACCEL_Y);
        break;
      case 1:
        Uart_SendData_float(DATA_TYPE_ANV,GYRO_X);
        break;
      case 2:
        Uart_SendData_float(DATA_TYPE_DIA,dip_angle);
        break;
      case 3:
        Uart_SendData_float(DATA_TYPE_DIR,direction);
        break;
      case 4:
        Uart_SendData_int(DATA_TYPE_LMT,Motor_A_Count);
        break;
      case 5:
        Uart_SendData_int(DATA_TYPE_RMT,Motor_B_Count);
        break;
    }
    i++;
    if(i == 6)
      i = 0;
    vTaskDelay(200);
  }
}

接收

接收部分,我采用的是中断方式。由于 ESP32 发送过来的数据都是 1 个字节的命令,因此不需要在接收时进行拼接。

实现如下:

  • 使能串口接收中断,其他串口实现代码在下面这篇文章里有:

void Uart1_Init(void){
  USART_TX_GPIO_CLK_ENABLE();
  USART_RX_GPIO_CLK_ENABLE();

  huart1.Instance = USARTx;
  huart1.Init.BaudRate = USART_BAUDRATE;//波特率:115200
  huart1.Init.WordLength = UART_WORDLENGTH_8B;//数据位长度:8
  huart1.Init.StopBits = UART_STOPBITS_1;//停止位长度:1
  huart1.Init.Parity = UART_PARITY_NONE;//校验位:无
  huart1.Init.Mode = UART_MODE_TX_RX;//通信模式:收发
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流量控制:关闭
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;//过采样模式

  HAL_UART_Init(&huart1);
  HAL_UART_Receive_IT(&huart1,&RxBuffer,1);//使能串口接收中断
}
  • 串口接收中断回调函数(该函数在接收到 1 字节时调用,还是在中断中执行)
uint8_t RxBuffer = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
  UNUSED(huart);
  BaseType_t xHigherPriorityTaskWoken,xResult;
  xHigherPriorityTaskWoken = pdFALSE;
  //按照接收的字节数据释放不同的事件,系统其他部分作出不同的处理
  switch (RxBuffer)
  {
  case CMD_FORWARD:
    xResult = xEventGroupSetBitsFromISR(state_events,EVENT_FORWARD,&xHigherPriorityTaskWoken);
    break;
  case CMD_RIGHT:
    xResult = xEventGroupSetBitsFromISR(state_events,EVENT_RIGHT,&xHigherPriorityTaskWoken);
    break;
  case CMD_LEFT:
    xResult = xEventGroupSetBitsFromISR(state_events,EVENT_LEFT,&xHigherPriorityTaskWoken);
    break;
  case CMD_BACK:
    xResult = xEventGroupSetBitsFromISR(state_events,EVENT_BACK,&xHigherPriorityTaskWoken);
    break;
  }
  HAL_UART_Receive_IT(&huart1,(uint8_t*)&RxBuffer,1);//重新使能接收中断
  if(xResult != pdFAIL)
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

ESP32 部分

初始化

#define UARTx UART_NUM_0
#define UART_BAUD_RATE 115200

static const int RX_BUF_SIZE = 2048;
static const int TX_BUF_SIZE = 128;

void UartSTM32_init(void){
    uart_config_t uartConfig = {
            .baud_rate = UART_BAUD_RATE,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
            .source_clk = UART_SCLK_APB,
    };
    //安装UART驱动
    ESP_ERROR_CHECK(uart_driver_install(UARTx,RX_BUF_SIZE * 2,TX_BUF_SIZE * 2,0,NULL,0));
    //配置串口参数
    ESP_ERROR_CHECK(uart_param_config(UARTx,&uartConfig));
    //设置串口对应引脚
    ESP_ERROR_CHECK(uart_set_pin(UARTx,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE));
}

配置引脚时,不要将 RX 和 TX 替换为 GPIO_NUM_43GPIO_NUM_44,否则串口不会正常工作。

发送

void TX_task(void *pvParameter){
    vTaskDelay(1000);//等待接收端准备好
    static uint8_t i = 3;
    uint8_t * tx_buff = (uint8_t*) malloc(1);
    while (1){
        //这里的代码还没定型,后面还会修改
        switch (i) {
            case 0:*tx_buff = CMD_FORWARD;break;
            case 1:*tx_buff = CMD_LEFT;break;
            case 2:*tx_buff = CMD_RIGHT;break;
            case 3:*tx_buff = CMD_BACK;break;
        }
        //发送
        uart_write_bytes(UARTx,(const uint8_t*)tx_buff, 1);
        vTaskDelay(100/portTICK_PERIOD_MS);
    }
}

接收

void RX_task(void *pvParameter){
    char* data = (char*) malloc(RX_BUF_SIZE+1);
    while (1){
        //读取串口数据,每次等待200ms
        uart_read_bytes(UARTx,data,RX_BUF_SIZE,200 / portTICK_PERIOD_MS);
        //提取首字节
        char data_type = *data;
        //按照数据类型转换数据,并更新
        switch (data_type) {
            case DATA_TYPE_ACC:accel = strtod(data+1,NULL);break;
            case DATA_TYPE_ANV:angular_velocity = strtod(data+1,NULL);break;
            case DATA_TYPE_DIA:dip_angle = strtod(data+1,NULL);break;
            case DATA_TYPE_DIR:direction = strtod(data+1,NULL);break;
            case DATA_TYPE_LMT:left_motor = (int)strtol(data+1,NULL,10);break;
            case DATA_TYPE_RMT:right_motor = (int)strtol(data+1,NULL,10);break;
        }
    }
    free(data);
}

给其中一个芯片烧录时,如果两个芯片的串口线还连在一起,一定要断掉另外一个芯片的电,否则会烧录失败

最后修改:2023 年 12 月 15 日
如果觉得我的文章对你有用,请随意赞赏