本来以为实现起来不难,没想到这么多坑,这里记录一下实现过程。
软硬件资源
-
硬件
-
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_43
和 GPIO_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);
}
给其中一个芯片烧录时,如果两个芯片的串口线还连在一起,一定要断掉另外一个芯片的电,否则会烧录失败