【星鸿派】OpenHarmony WS63 UART 开发避坑指南

电子说

1.4w人已加入

描述

问题背景


• 芯片型号:星鸿派-海思 WS63(星闪芯片)
• 操作系统:OpenHarmony LiteOS-M
• 开发板:标准 WS63 开发板
• 串口配置:UART2,GPIO_7(TX)/ GPIO_8(RX),波特率 9600

通过 UART2 接收外部设备发送的数据,收到后立即回显。按照常规思路,我使用了 IoTUartRead() 函数进行轮询读取,设置 rxBlock 为非阻塞模式,并在循环中加入 osDelay(100) 让出 CPU。

代码烧录后,串口初始化成功,但很快发现:
1. 系统响应变慢,其他线程几乎停止工作
2. CPU 占用率飙升至 100%,日志显示是串口收发的线程占用100%
3. 程序里还有wifi等线程。因为CPU被串口占满,程序不断重启。
4. 即使增加了 osDelay,问题依然存在。

串口代码是按照官方资料中带的uart例程写的,结果发现它占用了过多的CPU,在我的程序中,存在问题。

原始问题代码:

 

#define ECHO_UART_PORT  2
#define UART_TRANSFER_SIZE 16

unsigned char uartReadBuff[UART_TRANSFER_SIZE] = {0};

static void test_task(void *arg)
{
    // 串口初始化...
    IotUartAttribute uart_attr = {
        .baudRate = 9600,
        .dataBits = IOT_UART_DATA_BIT_8,
        .stopBits = IOT_UART_STOP_BIT_1,
        .parity = IOT_UART_PARITY_NONE,
        .rxBlock = IOT_UART_BLOCK_STATE_NONE_BLOCK,  // 非阻塞模式
        .txBlock = IOT_UART_BLOCK_STATE_NONE_BLOCK,
        .pad = 0,
    };
    IoTUartInit(ECHO_UART_PORT, &uart_attr);

    while (1) {
        // 读取串口数据
        len = IoTUartRead(ECHO_UART_PORT, uartReadBuff, UART_TRANSFER_SIZE);
        
        if (len > 0) {
            IoTUartWrite(ECHO_UART_PORT, uartReadBuff, len);  // 回显
        }
        
        osDelay(100);  // 让出CPU
    }
}

 

我尝试了多种方法:
1. 增大延时:osDelay(100) → osDelay(500),略有改善但 CPU 仍高
2. 改用阻塞模式:rxBlock = IOT_UART_BLOCK_STATE_BLOCK,问题依旧
3. 检查引脚配置:确认 GPIO_7/GPIO_8 对应 UART2,配置无误
4. 检查栈空间:增大任务栈到 8KB,无改善

问题根因分析

问题的核心在于对 IoTUartRead "非阻塞模式"的误解。IOT_UART_BLOCK_STATE_NONE_BLOCK 并不是说"没数据立刻返回",而是指"不阻塞等待对方发完所有请求的字节数"。驱动内部仍然会轮询一段时间,这个轮询是纯 CPU 忙等(busy-loop),不会让出 CPU。

 

while (1) {
    len = IoTUartRead(...);   // 内部忙等 50~200ms,CPU 100%
    if (len > 0) { ... }
    osDelay(100);              // 100ms 喘息,但占比太小
}

 

IoTUartRead 的轮询实现方式与 RTOS 的协作式调度不兼容。在OpenHarmony 中,线程通过 osDelay 主动让出 CPU,但如果驱动内部持续忙等,线程就无法真正休眠,导致 CPU 被占满。
这不是应用代码的问题,而是驱动层实现的问题。要解决这个问题,必须绕过轮询,改用中断方式。

解决方法

这不是应用代码的问题,而是驱动层实现的问题。要解决这个问题,必须绕过轮询,改用中断方式。

关键优势:
• 没有数据时,线程处于休眠状态,CPU 占用接近 0%
• 有数据时,硬件中断立即唤醒线程,响应实时
• 完全避免了轮询带来的 CPU 浪费

步骤 1:注册中断回调

 


// 注册中断接收回调
uapi_uart_register_rx_callback(
   ECHO_UART_PORT,                           // UART 端口号
   UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE,  // 触发条件
   1,                                        // 触发阈值(字节数)
   uart_rx_callback                          // 回调函数
);

 

步骤 2:实现回调函数

 


static void uart_rx_callback(const void *buffer, uint16_t length, bool error)
{
   (void)error;
   if (buffer == NULL || length == 0) {
       return;
   }
   
   uint8_t *data = (uint8_t *)buffer;
   uint16_t copyLen = (length < UART_TRANSFER_SIZE) ? length : UART_TRANSFER_SIZE;
   memcpy(uartReadBuff, data, copyLen);
   uartRxLen = copyLen;
   
   // 唤醒任务线程
   osEventFlagsSet(uartEventId, UART_RX_EVENT);
}

 

回调函数注意事项:
• 在中断上下文执行,必须快速完成
• 只做数据拷贝和事件通知,不要做耗时操作
• 不要调用 printf、osDelay 等阻塞函数

完整代码

以下是经过验证的完整代码,可直接用于 WS63 开发:

 


#include <  stdio.h  >
#include <  string.h  >
#include <  stdbool.h  >
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "common_def.h"
#include "iot_errno.h"
#include "iot_watchdog.h"
#include "iot_gpio.h"
#include "iot_gpio_ex.h"
#include "iot_uart.h"
#define TEST_TASK_PRIO          26
#define TEST_TASK_STACK_SIZE    0x1000
#define ECHO_UART_PORT          2
#define UART_TRANSFER_SIZE      256
static uint8_t uartReadBuff[UART_TRANSFER_SIZE] = {0};
static volatile uint16_t uartRxLen = 0;
static osEventFlagsId_t uartEventId = NULL;
#define UART_RX_EVENT  0x01
// ========== 中断接收回调函数 ==========
static void uart_rx_callback(const void *buffer, uint16_t length, bool error)
{
   (void)error;
   if (buffer == NULL || length == 0) {
       return;
   }
   uint8_t *data = (uint8_t *)buffer;
   uint16_t copyLen = (length < UART_TRANSFER_SIZE) ? length : UART_TRANSFER_SIZE;
   memcpy(uartReadBuff, data, copyLen);
   uartRxLen = copyLen;
   osEventFlagsSet(uartEventId, UART_RX_EVENT);
}
// ========== 测试任务 ==========
static void test_task(void *arg)
{
   unused(arg);
   printf("start sample_03_uart (interrupt mode)rn");
   osDelay(100);
   // 1. GPIO 引脚复用
   IoTGpioInit(IOT_IO_NAME_GPIO_7);
   IoSetFunc(IOT_IO_NAME_GPIO_7, 2);
   IoTGpioInit(IOT_IO_NAME_GPIO_8);
   IoSetFunc(IOT_IO_NAME_GPIO_8, 2);
   // 2. 串口参数配置
   IotUartAttribute uart_attr = {
       .baudRate = 9600,
       .dataBits = IOT_UART_DATA_BIT_8,
       .stopBits = IOT_UART_STOP_BIT_1,
       .parity = IOT_UART_PARITY_NONE,
       .rxBlock = IOT_UART_BLOCK_STATE_NONE_BLOCK,
       .txBlock = IOT_UART_BLOCK_STATE_NONE_BLOCK,
       .pad = 0,
   };
   // 3. 串口初始化
   IoTUartDeinit(ECHO_UART_PORT);
   if (IoTUartInit(ECHO_UART_PORT, &uart_attr) != 0) {
       printf("uart%d init failed!rn", ECHO_UART_PORT);
       return;
   }
   // 4. 创建事件标志
   uartEventId = osEventFlagsNew(NULL);
   if (uartEventId == NULL) {
       printf("event flags create failed!rn");
       return;
   }
   // 5. 注册中断接收回调
   if (uapi_uart_register_rx_callback(ECHO_UART_PORT,
           UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE,
           1, uart_rx_callback) != 0)
   {
       printf("uart register rx callback failed!rn");
       return;
   }
   // 6. 主循环
   while (1) {
       uint32_t flags = osEventFlagsWait(uartEventId, UART_RX_EVENT,
                                          osFlagsWaitAny, osWaitForever);
       if (flags & UART_RX_EVENT) {
           IoTUartWrite(ECHO_UART_PORT, uartReadBuff, uartRxLen);
           memset(uartReadBuff, 0, UART_TRANSFER_SIZE);
           uartRxLen = 0;
       }
   }
}
static void test_entry(void)
{
   osThreadAttr_t attr;
   attr.name = "test_task";
   attr.stack_size = TEST_TASK_STACK_SIZE;
   attr.priority = osPriorityBelowNormal7;
   if (osThreadNew(test_task, NULL, &attr) == NULL) {
       printf("Failed to create test_task!n");
   }
}
APP_FEATURE_INIT(test_entry);

审核编辑 黄宇

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分