跳转至

UART使用指南

UART使用指南*

第一章 功能特性*

主要特性

  • 支持 5,6,7,8 位数据长度,可选校验方式(奇校验偶校验无校验),支特 1/1.5/2 个停止位
  • 支持奇、偶校验方式或者无校验
  • 可编程波特率
  • 支持 FIFO
  • 支持 DMA
  • 支持硬件流控
  • 支持自动低功耗
  • 支持两组串口
  • 支持的最大波特率 3M

第二章 使用示例*

UART的接收和发送,支持:

  • 同步接收和发送(阻塞模式)
  • 异步接收和发送(中断模式)
  • 异步接收和发送(DMA模式)

2.1 API 接口描述*

/*
 *     NationalChip Hardware Abstract Layer
 * Copyright (C) 2001-2023 NationalChip Co., Ltd
 *              ALL RIGHTS RESERVED
 *
 *  uart.h: uart driver header file
 */

#ifndef __GX_UART_H__
#define __GX_UART_H__

#define UART_PORT_MAX 2

#define GX_UART_PORT0 0
#define GX_UART_PORT1 1

/* Generic uart API */
/**
 * @brief 串口初始化
 *   只需要调用一次,且串口在时候过程中不能调用
 *
 * @param port 端口
 * @param baudrate 波特率, 支持标准波特率, 一般为 115200
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_init(int port, uint32_t baudrate, int flow_ctrl);

/**
 * @brief 串口退出
 *
 * @param port 端口
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_exit(int port);

/**
 * @brief 输出一个字符
 *
 * @param port 端口
 * @param ch 要输出的字符
 */
void gx_uart_putc(int port, int ch);

/**
 * @brief 输出一个字符, 如果是换行,转换成回车输出
 *
 * @param port 端口
 * @param ch 要输出的字符
 */
void gx_uart_compatible_putc(int port, int ch);

/**
 * @brief 清空串口 fifo
 *
 * @param port 端口
 */
void gx_uart_empty_fifo(int port);

/**
 * @brief 获取串口 fifo 深度
 *
 * @param port 端口
 */
int gx_uart_get_fifo_depth(int port);

/**
 * @brief 获取串口 dlf size
 *
 * @param port 端口
 */
int gx_uart_get_dlf_size(int port);

/**
 * @brief 获取串口 IP 版本
 *
 * @param port 端口
 */
int gx_uart_get_ip_version(int port);

/**
 * @brief 获取一个字符
 *
 * @param port 端口
 *
 * @return int 获取的字符
 */
int gx_uart_getc(int port);

/**
 * @brief 尝试获取一个字符,如果没有获取到字符,立马返回
 *
 * @param port 端口
 * @param c 获取到的字符
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_try_getc(int port, char *c);

/**
 * @brief 判断 fifo 中是否有数据
 *
 * @param port 端口
 * @return int 结果
 * @retval 0 没有数据
 * @retval 1 有数据
 */
int gx_uart_can_read(int port);

/**
 * @brief 同步读取指定长度的数据
 *        如果没有读到指定的数据,会一直等待
 *
 * @param port 端口
 * @param buf 读取到的数据保存buf
 * @param len 数据长度
 * @return int 实际读取到的数据长度
 */
int gx_uart_read(int port, unsigned char *buf, int len);

/**
 * @brief 读取指定长度的数据, 指定时间内没有读到指定长度的数据,立即返回
 *
 * @param port 端口
 * @param buf 读取到的数据保存buf
 * @param len 数据长度
 * @param timeout_ms 超时时间
 * @return int 实际读取到的数据长度
 */
int gx_uart_read_non_block(int port, unsigned char *buf, int len, unsigned int timeout_ms);


/**
 * @brief 同步写指定长度的数据
 *        以阻塞的方式写入 len
 *
 * @param port 端口
 * @param buf 需要写出的数据buf
 * @param len 数据长度
 * @return int 实际写出数据长度
 */
int gx_uart_write(int port, const unsigned char *buf, int len);

/**
 * @brief 串口暂停
 *
 * @param void
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_suspend(int port);

/**
 * @brief 串口恢复
 *
 * @param void
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_resume(int port);

int gx_uart_wakeup_enable(int port, void (*wakeup_cb)(int port));

int gx_uart_wakeup_disable(int port);

/**
 * @brief 设置串口工作频率
 *   设置串口的输入频率,调用该接口后要调用gx_uart_init重新初始化串口
 *
 * @param port 端口
 * @param freq 频率
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_set_freq(int port, int freq);

//-------------------------------------------------------------------------------------------------
// FIFO Related

/**
 * @brief 串口发送 fifo 门限值
 */
typedef enum {
    GX_UART_FIFO_SEND_THRESHOLD_EMPTY,      ///< empty
    GX_UART_FIFO_SEND_THRESHOLD_2,          ///< 2 byte
    GX_UART_FIFO_SEND_THRESHOLD_QUARTER,    ///< 25%
    GX_UART_FIFO_SEND_THRESHOLD_HALF,       ///< 50%
    GX_UART_FIFO_SEND_THRESHOLD_NONE,       // None
} GX_UART_FIFO_SEND_THRESHOLD;

/**
 * @brief 串口接收 fifo 门限值
 */
typedef enum {
    GX_UART_FIFO_RECV_THRESHOLD_ONE_BYTE,   ///< 1 byte
    GX_UART_FIFO_RECV_THRESHOLD_QUARTER,    ///< 25%
    GX_UART_FIFO_RECV_THRESHOLD_HALF,       ///< 50%
    GX_UART_FIFO_RECV_THRESHOLD_FULL,       ///< Almost Full
    GX_UART_FIFO_RECV_THRESHOLD_NONE,       // None
} GX_UART_FIFO_RECV_THRESHOLD;

/**
 * @brief 设置串口发送 fifo 中断触发门限
 *
 * @param port 端口
 * @param threshold 门限值, 详细说明请参考 gxdocref GX_UART_FIFO_SEND_THRESHOLD
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_set_send_fifo_threshold(int port, GX_UART_FIFO_SEND_THRESHOLD threshold);

/**
 * @brief 设置接收 fio 中断触发门限
 *
 * @param port 端口
 * @param threshold 门限值, 详细说明请参考 gxdocref GX_UART_FIFO_RECV_THRESHOLD
 * @return int 是否成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_set_recv_fifo_threshold(int port, GX_UART_FIFO_RECV_THRESHOLD threshold);

/**
 * @brief 清空发送 fifo
 *
 * @param port 端口
 */
void gx_uart_flush_send_fifo(int port);

/**
 * @brief 清空接收 fifo
 *
 * @param port 端口
 */
void gx_uart_flush_recv_fifo(int port);


//-------------------------------------------------------------------------------------------------
// Async API Mode 1 (Interrupt Mode)

typedef int (*UART_CAN_SEND_CALLBACK)(int port, int length, void *priv);
typedef int (*UART_CAN_RECV_CALLBACK)(int port, int length, void *priv);

/**
 * @brief 开始异步接收串口数据
 *
 * @param port 端口
 * @param callback 串口有数据可以接收回调函数
 * @param priv 回调函数参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_start_async_send(int port, UART_CAN_SEND_CALLBACK callback, void *priv);

/**
 * @brief 停止异步接收串口数据
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_stop_async_send(int port);

/**
 * @brief 开始异步发送串口数据
 *
 * @param port 端口
 * @param callback 可以发送串口数据回调函数
 * @param priv 回调函数参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_start_async_recv(int port, UART_CAN_RECV_CALLBACK callback, void *priv);

/**
 * @brief 停止异步发送串口数据
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_stop_async_recv(int port);

// No check FIFO status in these function
/**
 * @brief 发送串口数据, 但是不检查 fifo 是否满,
 *     和 gx_uart_start_async_send 配合使用
 *
 * @param port 端口
 * @param buffer 要发送的数据
 * @param length 数据长度
 * @return int 实际发送数据长度
 */
int gx_uart_send_buffer(int port, unsigned char *buffer, int length);

/**
 * @brief 接收串口数据, 但是不检查 fifo 中是否有数据
 *     和 gx_uart_start_async_recv 函数配合使用
 *
 * @param port 端口
 * @param buffer 接收数据 buffer
 * @param length 数据长度
 * @return int 实际接收数据长度
 */
int gx_uart_recv_buffer(int port, unsigned char *buffer, int length);

//-------------------------------------------------------------------------------------------------
// Async API Mode 2 (DMA Mode)
//   In Leo, it is implemented by interrupt mode

typedef int (*UART_SEND_DONE_CALLBACK)(int port, void *priv);
typedef int (*UART_RECV_DONE_CALLBACK)(int port, void *priv);

/**
 * @brief 异步发送串口数据
 *
 * @param port 端口
 * @param buffer 需要发送的数据
 * @param length 数据长度
 * @param callback 数据发送完成回调
 * @param priv 回调参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_send_buffer(int port, unsigned char *buffer, int length, UART_SEND_DONE_CALLBACK callback, void *priv);

/**
 * @brief 异步接收串口数据
 *
 * @param port 端口
 * @param buffer 接收数据 buffer
 * @param length 接收数据的长度
 * @param callback 数据接收完成回调
 * @param priv 回调参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_recv_buffer(int port, unsigned char *buffer, int length, UART_RECV_DONE_CALLBACK callback, void *priv);

/**
 * @brief 停止串口异步发送接口
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_send_buffer_stop(int port);

/**
 * @brief 停止串口异步接收接口
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_recv_buffer_stop(int port);

/**
 * @brief 异步发送串口数据
 *
 * @param port 端口
 * @param buffer 需要发送的数据
 * @param length 数据长度
 * @param callback 数据发送完成回调
 * @param priv 回调参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_send_buffer_intr(int port, unsigned char *buffer, int length, UART_SEND_DONE_CALLBACK callback, void *priv);

/**
 * @brief 异步接收串口数据
 *
 * @param port 端口
 * @param buffer 接收数据 buffer
 * @param length 接收数据的长度
 * @param callback 数据接收完成回调
 * @param priv 回调参数
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_recv_buffer_intr(int port, unsigned char *buffer, int length, UART_RECV_DONE_CALLBACK callback, void *priv);

/**
 * @brief 停止串口异步发送接口
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_send_buffer_stop_intr(int port);

/**
 * @brief 停止串口异步接收接口
 *
 * @param port 端口
 * @return int 函数是否执行成功
 * @retval 0 成功
 * @retval -1 失败
 */
int gx_uart_async_recv_buffer_stop_intr(int port);

#endif

/** @}*/

2.2 初始化*

Apus支持两组串口,分别是 UART0 和 UART1。

通过硬件原理图确认好使用哪一组UART后,就可以进行如下初始化:

gx_uart_init(0, 115200, 0); // 初始化UART0,波特率115200,不支持流控
gx_uart_init(1, 9600, 1); // 初始化UART1,波特率9600,支持流控

2.3 同步阻塞方式*

// 同步阻塞读
char buf[32] = {0};
int len = gx_uart_read(0, buf, 32);
// 同步阻塞,超时读
char buf[32] = {0};
int len = gx_uart_read_non_block(0, buf, 32, 1000);
// 同步阻塞写
char buf[32] = {0x01,0x02};
int len = gx_uart_write(0, buf, 32);

2.4 异步DMA非阻塞方式*

//sdk_config.h 里打开如下宏
#define CONFIG_USING_UART_DMA_TRANS    (1)

// 初始化
gx_dma_init(); // 如果要使用UART DMA的方式,需要先初始化DMA
gx_uart_init(0, 115200, 0);
// 异步DMA发送
static char data[64] = {0x01, 0x02};

static int uart_tx_complete(int port, void *priv)
{
    printf("send complete\n");
    return 0;
}

void test_dma_send(void)
{
    gx_uart_async_send_buffer(0, data, 64, uart_tx_complete, NULL); // 调用该接口不会阻塞
}
// 异步DMA接收
static char data[64] = {0x0};

static int uart_rx_complete(int port, void *priv) // 只有接收满64字节,才会触发回调
{
    printf("receive complete\n");
    gx_uart_async_recv_buffer(0, data, 64, uart_rx_complete, NULL); // 如果想继续接收的话,调用这个
    return 0;
}

void test_dma_recv(void)
{
    gx_uart_async_recv_buffer(0, data, 64, uart_rx_complete, NULL); // 调用该接口不会阻塞
}

2.5 中断方式接收*

提供一个高效率中断方式接收串口数据的例子,能及时拿数据和避免丢数据的问题

核心原理是,通过中断告知有串口数据需要接收。然后在中断回调里,调用非阻塞的读接口,并且一次性读大量的数据,并返回实际读到的数据长度

static int _uart_read_non_block(int port, unsigned char *buf, int len, unsigned int timeout_ms)
{
    int rlen = 0;
    uint32_t start;

    start = gx_get_time_ms();
    while (rlen < len) {
        if (gx_hal_uart_can_read(&uart_config[port])) {
            buf[rlen] = gx_hal_uart_getc(&uart_config[port]);
            rlen++;
        }

        if (gx_get_time_ms() - start > timeout_ms)
            break;
    }

    return rlen;
}

int gx_uart_read_non_block(int port, unsigned char *buf, int len, unsigned int timeout_ms)
{
    if (!_uart_port_valid(port))
        return -1;

    return _uart_read_non_block(port, buf, len, timeout_ms);
}

unsigned char tmp[128] = {0};
static int uart_can_recv_callback(int port, int length, void *priv)
{
    //printf("port %d, can recv : %d\n", port, length);
    int len = gx_uart_read_non_block(1, tmp, 128, 0);
    //printf("len = %d\n", len);
    return 0;
}

void main()
{
    gx_uart_init(1, 115200, 0);
    gx_uart_start_async_recv(1, uart_can_recv_callback, NULL);
}

另外gx_uart_read_non_block会一直占着cpu,这里提供一个不占用cpu的接口 uart_read_timeout

// gx_uart_read_non_block会一直占着cpu,这里提供一个不占用cpu的接口
int uart_read_timeout(int port, unsigned char *buf, int len, unsigned int timeout_ms)
{
    int rlen = 0;
    int wait_tick = 0;

    while(rlen < len) {
        if(gx_uart_try_getc(port, &buf[rlen]) == 0) {
            rlen++;
            wait_tick = 0;
        } else {
            wait_tick++;
            if(chip_os_time_ticks_to_ms(wait_tick) > timeout_ms) {
                break;
            }
            chip_os_task_sleep(1);
        }
    }

    return rlen;
}

2.6 串口1使能*

以 8302B_dev 开发板为例

第一步:打开:apus\platform\boards\gx8302b_dev\board_config.h

第二步:把 BOARD_HAS_UART1 设置为 1

UART1_RX_PIN 设置为 P1_6

UART1_TX_PIN 设置为 P1_7

注意

不一定非得 P1_6 P1_7,可以根据硬件情况,自己选择合适的管脚用于复用

第三步:在开发板上,把 P1_6, P1_7 ,地线和 PC 端的串口连接起来

第四步:在自己的应用 main 函数里,初始化串口 1,调用 gx_uart_init

// 同步接收
int main(void)
{
    gx_uart_init(1, 115200, 0);
    gx_uart_putc(1, 'A');

    char c = gx_uart_getc(1); // 将阻塞住,直到接收到一个串口数据 
    printf("get c = %c\n", c);
    return 0;
}

第三章 UART OTA 升级*

在不连接上蓝牙的情况下即 "离线模式",GX830X 芯片支持通过 "串口透传" 的方式或者USB的方式来实现固件OTA升级。

GX830X 芯片支持空片升级,也就是说,GX830X 本身没有烧录过任何固件的情况下,也可以通过串口或者USB方式进行升级。这个升级安全可靠。因为在升级过程中断电或者出现异常,导致设备变砖,仍然可以重新按住BOOT键上电复位后,重新进行OTA升级。升级过程,和 GX830X 本身跑的内部固件没有任何关系。

在这里,只介绍 UART OTA 的方式。USB OTA 的方式适合主控是linux或者Android系统。

重要

仅支持 UART0 升级

3.1 硬件连接*

硬件连接图

硬件具体引脚图

功能 引脚
URAT0_RX P0_5
URAT0_TX P0_6
BOOT P0_7
RESET P3_0

3.2 UART OTA 流程图*

3.3 非流式升级*

3.3.1 参考代码下载和说明*

我们提供了非流式纯C代码编写的升级例子,支持在PC windows或者linux上编译,验证。

用户可以在PC上,通过串口连接我们的开发板,使用我们的参考代码验证。然后再移植到自己的机器上。

apus_uart_ota.zip (http://yun.nationalchip.com:10000/l/wFKixi)

升级例程代码说明

  • main.c 主代码
  • porting.c 主要移植的接口
  • porting.h 主要移植的接口函数的头文件
  • crc.c 移植crc32校验接口
  • apus_nre.boot bootloader镜像
  • makefile Makefile文件
  • apus.bin 需要升级的固件
  • Readme.md 需要阅读关注下
3.3.2 windows 环境验证*
  • Windows 环境需要先安装 gcc 编译器 mingw,并配置好环境变量,若没有,则执行apus_uart_ota.zip里面的mingw-get-setup.tar.gz里面的**mingw-get-setup.exe**安装一下

  • 编译环境搭建完毕后,解压 apus_uart_ota.zip

  • 进入 apus_uart_ota 目录下,注意当前路径下不能含有中文,打开cmd命令窗口,输入命令 mingw32-make 生成可执行文件 main.exe

mingw32-make
  • 如下图将 GX830X 开发板连上 PC

  • 执行下面的命令后,手动操作: 按住BOOT键后,再单击RESET键给芯片复位上电

  • 观察终端LOG有没有继续跑下去, 则松开按键等待烧录完成, 则重新执行手动

  • 串口设备号不一定为COM0,根据本机实际情况修改,可在电脑设备管理器/端口(COM和LPT)/ 下看到对应的串口设备号
.\main.exe COM0 1000000 apus_nre.boot apus.bin
  • 出现 image success! 的提示即固件升级成功

  • 升级完成后,手动单击一下RESET键,然后通过串口0(波特率115200)看到开机log(如下图)

3.3.3 ubuntu linux 环境验证*
  • 编译环境搭建完毕后,解压 apus_uart_ota.zip

  • 进入 apus_uart_ota 目录下,注意当前路径下不能含有中文,打开终端输入命令 make linux 生成可执行文件 main.elf

make linux
  • 仅执行命令不同,其他的步骤和 windows 的一致
sudo ./main.elf /dev/ttyUSB0 1000000 apus_nre.boot apus.bin

3.4 流式+网络透传升级*

3.4.1 参考代码下载和说明*

我们提供了流式+网络透传的纯C代码编写的升级例子,支持在PC linux上编译,验证。

用户可以在PC上,通过串口连接我们的开发板,使用我们的参考代码验证。然后再移植到自己的机器上。

apus_uart_ota.zip (http://yun.nationalchip.com:10000/l/OFi4sG)

升级例程代码说明

  • main_ota.c 主代码
  • porting.c 主要移植的接口
  • porting.h 主要移植的接口函数的头文件
  • crc.c 移植crc32校验接口
  • tool/gxscpu.boot bootloader镜像
  • makefile Makefile文件
  • tool/apus.bin 需要升级的固件
3.4.2 ubuntu linux 环境验证*
3.4.2.1 编译*
  • 编译环境搭建完毕后,解压 apus_uart_ota.zip

  • 进入 apus_uart_ota 目录下,注意当前路径下不能含有中文,打开终端输入命令 make 生成可执行文件 main_ota 和 pc_server

make
3.4.2.2 固件合成*
  • 将boot文件与apus固件按如下格式合成
区域类型 内容 地址 长度
固定区域 gxscpu.boot Boot header 0x00 32 byte
Stage1 0x20 8 k
Stage2 0x2020 38 k
apus.bin len and crc 0x15020 8 byte (4byte + 4byte)
可变区域 apus.bin 0x15028 Image len
  • 把apus.bin换成自己编译出来的 tool/apus.bin , 进入 /tool/ 目录执行
python3 bin_merge.py
  • 合成出 apus_out.bin
3.4.2.3 运行*

Server 端执行:

sudo ./pc_server ./tool/apus_out.bin

Client 端执行:

sudo ./main_ota /dev/ttyUSB0 100000
  • 执行上面的命令后,手动操作: 按住BOOT键后,再单击RESET键给芯片复位上电

  • 出现 image success! 的提示即固件升级成功

  • 升级完成后,手动单击一下RESET键,然后通过串口0(波特率115200)看到开机log(如下图)

3.5 移植注意事项*

  • 升级过程分两段,一段是加载 bootloader,等 bootloader 启动后再加载要烧录的固件。因此,开发者需要把 apus_nre.boot 固化到自己的升级代码里,或者保存在主控的 flash 上。apus_nre.boot 70K 大小。

  • 升级过程,串口波特率可以先用我们代码推荐的,如果发现不稳定,可以降低波特率再测试,以找到个合适的波特率。

  • 升级的协议流程,开发者可以不需要关注,直接移植即可。但是,需要关注 porting.c,把里面的串口接收接口换成自己平台的。
  • 上电复位 GX830X 的时候,要注意上电时序, 先按住 BOOT 后再单击 RESET 复位或者重新给GX830X上电。
  • 移植的时候,推荐使用我们参考例子给的超时时间。例子给的时间都比较长,这样稳定性会更好。而且这个时间,在 OTA 正常的话,是很短的。如果真的出现异常导致失败,这点超时对比整个 OTA 的流程来说,也是很短的时间。
  • 移植的时候,强烈推荐先拿我们的开发板飞线,进行调试,这样好排除PCBA硬件的问题干扰,在这个基础上调试成功后,再到 PCBA 上测试。在开发板调试遇到问题的话,可以非常方便的抓开发板的日志给我们分析定位。
  • 握手阶段失败,先确保上电时序是否正确,然后尽量尝试握手的超时时间设长点,比如 5 秒超时。