之前做了LED与KEY测试,今天我们来做一下uart的测试,uart原理图如下:

根据原理图及数据手册可知使用的是uart9,所以要准备uart9进行设置。延续上次的测试工程,增加uart相关配置,步骤如下:打开RA Smart Configurator配置软件,增加uart相关的的stacks,如下图

按照如上步骤操作后,进入如下画面:

通过原理图可知用的是uart9,所以按照如下的参数设置

设置好后,并保存,点击右上角的"Generate Project Content"按钮生成MDK工程及代码,如下图:

打开生成的工程,完成uart9_callback函数,如下:
static volatile int g_uart9_tx_complete = 0;
static volatile int g_uart9_rx_complete = 0;
void uart9_callback(uart_callback_args_t * p_args)
{
switch (p_args->event)
{
case UART_EVENT_TX_COMPLETE:
{
g_uart9_tx_complete = 1;
break;
}
case UART_EVENT_RX_COMPLETE:
{
g_uart9_rx_complete = 1;
break;
}
case UART_EVENT_RX_CHAR:
{
lwrb_write(&buff, (uint8_t*)&p_args->data, 1);
break;
}
default:
{
break;
}
}
}
void uart9_wait_for_tx(void)
{
while (!g_uart9_tx_complete);
g_uart9_tx_complete = 0;
}
void uart9_wait_for_rx(void)
{
while (!g_uart9_rx_complete);
g_uart9_rx_complete = 0;
}
通过UART_EVENT_RX_CHAR事件接收串口的数据,这里是用了循环队列存储接收到的数据,这里的循环队列移植的是开源的lwrb库,地址如下:
https://github.com/MaJerle/lwrb
wrb(Light-Weight Ring Buffer)是一个 纯 C、零依赖、单文件 的环形缓冲区库,移植 只要 1 个 .c + 1 个 .h ,没有平台代码, 连 malloc 都不需要。步骤如下:
- 把
lwrb/src/include/lwrb.h 和 lwrb/src/lwrb.c 拖进你的工程;
- 在代码里
#include "lwrb.h";
- 给缓冲区 分配内存 (全局/静态都行)
`
lwrb_t buff = {0};
uint8_t buff_data[256];
-
初始化
lwrb_init(&buff, buff_data, sizeof(buff_data));
-
读写直接用
lwrb_write(&buff, (uint8_t*)&p_args->data, 1);
char ch = '0'; lwrb_read(&buff,&ch,1);
就是这么简单,要想知道有没有收到数据,只要一直监控这个队列里有没有数据即可,代码如下
if (lwrb_get_full(&buff))
只要有数据就会返回数据长度,为0时表示没有接收到数据或者数据已经用完。
uart接收处理完了,我们还需要实现输出,实现printf函数,代码如下:
int fputc(int ch, FILE *f)
{
(void)f;
g_uart9.p_api->write(g_uart9.p_ctrl, (uint8_t const *const)&ch, 1);
uart9_wait_for_tx();
return ch;
}
这些的前提条件是要打开串口,代码如下:
g_uart9.p_api->open(g_uart9.p_ctrl, g_uart9.p_cfg);
现在就可以通过printf输出一些内容,通过 printf("eco-ra6e2!\r\n");进行简单的输出测试如下:

进行输入的测试,为了方便,实现scanf的方法,代码如下:
int fgetc(FILE *f)
{
uint8_t ch;
(void)f;
g_uart9.p_api->read(g_uart9.p_ctrl, &ch, 1);
uart9_wait_for_rx();
{
g_uart9.p_api->write(g_uart9.p_ctrl, (uint8_t const *const)&ch, 1);
uart9_wait_for_rx();
if (ch == '\r')
{
g_uart9.p_api->write(g_uart9.p_ctrl, '\n', 1);
uart9_wait_for_rx();
;
}
}
return (int)ch;
}
带回显,输入测试代码如下:
uint32_t a, b;
while (1)
{
printf("Please enter two number:\r\n");
scanf("%d%d", &a, &b);
printf("%d+%d=%d\r\n", a, b, a+b);
}
效果如下:

不过uart就实现这些功能感觉有点太low了,接下来我们实现一些比较高大上的功能。
使用过Linux的人员应该比较熟悉那个可以输入命令的控制台,接下来我们就移植一个开源的shell到开发板中,实现如linux那样的交互体验。我们要移植的是nr_micro_shell,开源地址https://gitee.com/nrush/nr_micro_shell
文档中关于移植的说明比较详细,也比较简单。添加所有文件到项目中,仅需实现shell_putc()函数,使用shell(c)获取字符流,即可实现支持Tab补全,历史命令查询,与linux命令行交互体验基本相同。由于上面已经实现了printf功能,顺带着putchar函数功能也就实现了,所以shell_putc()函数功能可以直接用putchar函数来代替,代码如下:
#define shell_putc(x) putchar((x))一个宏定义搞定。
shell(c)函数在源代码中已经实现了,只需要将串口接收到的字符传个shell函数即可。代码如下:
if (lwrb_get_full(&buff))
{
char ch = '0';
lwrb_read(&buff,&ch,1);
shell(ch);
}
void shell(char c)
{
keycode_t ret;
ret = get_keycode(&_default_sh, c);
KEY_RECORD(c);
run_key_func(ret, &_default_sh, c);
}
就是这么简单,然后再入口函数中调用shell初始化函数shell_init();
将程序编译好后下载到芯片中看一下效果,如下:

动态效果见上面视频
图中打印的信息经过自己修改过的,代码如下:
void shell_init(void)
{
_shell_init(&_default_sh);
#ifdef NR_SHELL_DEBUG
nr_shell_debug_log_init();
#endif
cmd_clear();
#ifdef NR_SHELL_SHOW_LOGO
shell_printf(" \r\n");
#if 0
#else
shell_printf(" __ ____\r\n");
shell_printf(" ___ ___ ___ _ __ __ _ / /_ ___|___ \\\r\n");
shell_printf(" / _ \\/ __/ _ \\ _____| '__/ _` | '_ \\ / _ \\ __) |\r\n");
shell_printf("| __/ (_| (_) |_____| | | (_| | (_) | __// __/ \r\n");
shell_printf(" \\___|\\___\\___/ |_| \\__,_|\\___/ \\___|_____|\r\n");
#endif
shell_printf(" \r\n");
shell_printf("Welcome to the nr_micro_shell v%s! Build time %s %s\r\n", NR_SHELL_VERSION, __DATE__, __TIME__);
#endif
print_prompt(&_default_sh);
}
这个信息内容形式可以通过工具生成,工具Text to ASCll Art Generator(TAAG)地址如下:https://patorjk.com/software/taag/#p=display&f=Big+Money-ne&t=&x=none&v=4&h=4&w=80&we=false
想要什么内容输入即可,然后选择自己喜欢的显示形式。这么多命令都是可以自己定义自己添加的,具体方法见源码说明。
到此,控制台功能已经实现了,但是有时候发现别人的项目输出调试信息五颜六色的,可以根据打印的调试信息类型显示不同的颜色,接下来也实现一下。
这里直接贴.c.h出代码,如下:
log.c
#include "log.h"
#if LOG_USING_COLOR == 1
#define memPrintHead CSI(31) \
" Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F" \
CSI(39) \
"\r\n"
#define memPrintAddr CSI(31)"0x%08x: "CSI(39)
#else
#define memPrintHead " Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n"
#define memPrintAddr "0x%08x: "
#endif
Log *logList[LOG_MAX_NUMBER] = {0};
static char logBuffer[LOG_BUFFER_SIZE];
#if LOG_USING_LOCK == 1
static void logLock(Log *log)
{
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] && logList[i]->active)
{
if (logList[i]->lock)
{
LOG_LOCK(logList[i]);
}
}
}
}
else if (log->lock)
{
LOG_LOCK(log);
}
}
static void logUnlock(Log *log)
{
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] && logList[i]->active)
{
if (logList[i]->unlock)
{
LOG_UNLOCK(logList[i]);
}
}
}
}
else if (log->unlock)
{
LOG_UNLOCK(log);
}
}
#endif
void logRegister(Log *log)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] == 0)
{
logList[i] = log;
return;
}
}
}
void logUnRegister(Log *log)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] == log)
{
logList[i] = 0;
return;
}
}
}
void logSetLevel(Log *log, LogLevel level)
{
logAssert(log, return);
log->level = level;
}
static void logWriteBuffer(Log *log, LogLevel level, char *buffer, short len)
{
(void)buffer;
#if LOG_USING_LOCK == 1
logLock(log);
#endif
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i]
&& logList[i]->active
&& logList[i]->level >= level)
{
logList[i]->write(logBuffer, len);
}
}
}
else if (log && log->active && log->level >= level)
{
log->write(logBuffer, len);
}
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif
}
void logWrite(Log *log, LogLevel level, const char *fmt, ...)
{
va_list vargs;
int len;
#if LOG_USING_LOCK == 1
logLock(log);
#endif
va_start(vargs, fmt);
len = vsnprintf(logBuffer, LOG_BUFFER_SIZE - 1, fmt, vargs);
va_end(vargs);
if (len > LOG_BUFFER_SIZE)
{
len = LOG_BUFFER_SIZE;
}
logWriteBuffer(log, level, logBuffer, (short)len);
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif
}
void logHexDump(Log *log, LogLevel level, void *base, unsigned int length)
{
unsigned char *address;
unsigned int len;
unsigned int printLen = 0;
if (length == 0 || (log != LOG_ALL_OBJ && log->level < level))
{
return;
}
#if LOG_USING_LOCK == 1
logLock(log);
#endif
len = (unsigned int)snprintf(logBuffer, LOG_BUFFER_SIZE - 1, "memory of 0x%08x, size: %d:\r\n%s",
(unsigned int)base, length, memPrintHead);
logWriteBuffer(log, level, logBuffer, (short)len);
len = length;
address = (unsigned char *)((unsigned int)base & (~0x0000000FU));
length += (unsigned int)base - (unsigned int)address;
length = (length + 15) & (~0x0000000FU);
while (length)
{
printLen += (unsigned int)sprintf(logBuffer + printLen, memPrintAddr, (unsigned int)address);
for (int i = 0; i < 16; i++)
{
if ((unsigned int)(address + i) < (unsigned int)base
|| (unsigned int)(address + i) >= (unsigned int)base + len)
{
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = ' ';
}
else
{
printLen += (unsigned int)sprintf(logBuffer + printLen, "%02x ", *(address + i));
}
}
logBuffer[printLen ++] = '|';
logBuffer[printLen ++] = ' ';
for (int i = 0; i < 16; i++)
{
if ((unsigned int)(address + i) < (unsigned int)base
|| (unsigned int)(address + i) >= (unsigned int)base + len)
{
logBuffer[printLen ++] = ' ';
}
else
{
if (*(address + i) >= 32 && *(address + i) <= 126)
{
printLen += (unsigned int)sprintf(logBuffer + printLen, "%c", *(address + i));
}
else
{
logBuffer[printLen ++] = '.';
}
}
}
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = '|';
logBuffer[printLen ++] = '\r';
logBuffer[printLen ++] = '\n';
logWriteBuffer(log, level, logBuffer, (short)printLen);
address += 16;
length -= 16;
printLen = 0;
}
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif
}
#if SHELL_USING_COMPANION == 1
void logSwitchLevel(Shell *shell)
{
SHELL_ASSERT(log, return);
log->level = (LogLevel)(log->level >= LOG_ALL ? LOG_NONE : (log->level + 1));
logPrintln("set log level: %d", log->level);
}
#endif
void uartLogWrite(char *buffer, short len)
{
if (len <= 0) return;
while (len--)
putchar((unsigned char)*buffer++);
}
Log uartLog = {
.write = uartLogWrite,
.active = 1,
.level = LOG_DEBUG
};
void log_init(void)
{
logRegister(&uartLog);
}
log.h
#ifndef __LOG_H__
#define __LOG_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <inttypes.h>
extern uint64_t getPlatformTicks();
#define LOG_VERSION "1.0.1"
#define SHELL_COMPANION_ID_LOG -2
#define LOG_USING_LOCK 0
#define LOG_BUFFER_SIZE 256
#define LOG_USING_COLOR 1
#define LOG_MAX_NUMBER 5
#define LOG_AUTO_TAG 1
#define LOG_END "\r\n"
#define LOG_TIME_STAMP getPlatformTicks()
#ifndef LOG_TAG
#define LOG_TAG __FUNCTION__
#endif
#ifndef LOG_ENABLE
#define LOG_ENABLE 1
#endif
#if LOG_USING_LOCK == 1
#define LOG_LOCK(log) log->lock(log)
#define LOG_UNLOCK(log) log->unlock(log)
#else
#define LOG_LOCK(log)
#define LOG_UNLOCK(log)
#endif
#define LOG_ALL_OBJ ((Log *)-1)
#define CSI_BLACK 30
#define CSI_RED 31
#define CSI_GREEN 32
#define CSI_YELLOW 33
#define CSI_BLUE 34
#define CSI_FUCHSIN 35
#define CSI_CYAN 36
#define CSI_WHITE 37
#define CSI_BLACK_L 90
#define CSI_RED_L 91
#define CSI_GREEN_L 92
#define CSI_YELLOW_L 93
#define CSI_BLUE_L 94
#define CSI_FUCHSIN_L 95
#define CSI_CYAN_L 96
#define CSI_WHITE_L 97
#define CSI_DEFAULT 39
#define CSI(code) "\033[" #code "m"
#if LOG_USING_COLOR == 1
#define ERROR_TEXT CSI(31) "E(%" PRIu64 ") %s:"
#define WARNING_TEXT CSI(33) "W(%" PRIu64 ") %s:"
#define INFO_TEXT CSI(32) "I(%" PRIu64 ") %s:"
#define DEBUG_TEXT CSI(34) "D(%" PRIu64 ") %s:"
#define VERBOSE_TEXT CSI(36) "V(%" PRIu64 ") %s:"
#else
#define ERROR_TEXT "E(%" PRIu64 ") %s:"
#define WARNING_TEXT "W(%" PRIu64 ") %s:"
#define INFO_TEXT "I(%" PRIu64 ") %s:"
#define DEBUG_TEXT "D(%" PRIu64 ") %s:"
#define VERBOSE_TEXT "V(%" PRIu64 ") %s:"
#endif
typedef enum
{
LOG_NONE = 0,
LOG_ERROR = 1,
LOG_WRANING = 2,
LOG_INFO = 3,
LOG_DEBUG = 4,
LOG_VERBOSE = 5,
LOG_ALL = 6,
} LogLevel;
typedef struct log_def
{
void (*write)(char *, short);
char active;
LogLevel level;
#if LOG_USING_LOCK == 1
int (*lock)(struct log_def *);
int (*unlock)(struct log_def *);
#endif
} Log;
#define logPrintln(format, ...) \
logWrite(LOG_ALL_OBJ, LOG_NONE, format "\r\n", ##__VA_ARGS__)
#define logFormat(text, level, fmt, ...) \
if (LOG_ENABLE) {\
logWrite(LOG_ALL_OBJ, level, text " " fmt "" CSI(0) LOG_END, \
LOG_TIME_STAMP, LOG_TAG,##__VA_ARGS__); }
#define logError(fmt, ...) \
logFormat(ERROR_TEXT, LOG_ERROR, fmt, ##__VA_ARGS__)
#define logWarning(fmt, ...) \
logFormat(WARNING_TEXT, LOG_WRANING, fmt, ##__VA_ARGS__)
#define logInfo(fmt, ...) \
logFormat(INFO_TEXT, LOG_INFO, fmt, ##__VA_ARGS__)
#define logDebug(fmt, ...) \
logFormat(DEBUG_TEXT, LOG_DEBUG, fmt, ##__VA_ARGS__)
#define logVerbose(fmt, ...) \
logFormat(VERBOSE_TEXT, LOG_VERBOSE, fmt, ##__VA_ARGS__)
#define logAssert(expr, action) \
if (!(expr)) { \
logError("\"" #expr "\" assert failed at file: %s, line: %d", __FILE__, __LINE__); \
action; \
}
#define logHexDumpAll(base, length) \
logHexDump(LOG_ALL_OBJ, LOG_ALL, base, length)
void logRegister(Log *log);
void logUnRegister(Log *log);
void logSetLevel(Log *log, LogLevel level);
void logWrite(Log *log, LogLevel level, const char *fmt, ...);
void logHexDump(Log *log, LogLevel level, void *base, unsigned int length);
void log_init(void);
#ifdef __cplusplus
}
#endif
#endif
将这两个文件添加到项目中,再入口函数中调用 log_init();初始化函数即可,前提是要串口功能已经初始化好并且实现了printf功能了,程序编译好后下载进mcu后查看效果如下

从图片上看,打印信息不仅进行了颜色区分,还包含毫秒时间戳、条用打印信息的函数及位置。
以上就是我今天的测试内容了,后面还会由其他测试内容,尽请期待。