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 秒超时。