第7章_瑞萨MCU零基础入门系列教程之UART

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总: https://blog.csdn.net/qq_35181236/article/details/132779862


第7章 UART

本章目标

  • 使用RASC快速操作UART
  • 理解它的内部流程,改进程序

7.1 sci_uart模块的使用

7.1.1 使用RASC配置

本节的源码是“0701_UART”。

使用RASC配置UART时,需要进行两方面的配置:引脚配置、UART本身的配置。

首先进行引脚的配置,先打开原理图确认使用哪一个UART,比如引脚使用TXD0/RXD0、TXD1/RXD1、……中的哪一个,电路图如下,它使用TXD7/RXD7,记住这个编号7(TXDn/RXDn使用SCIn):

然后在RASC配置界面点击Pins页面,在左侧找到“Peripherals > Connectivity:SCI”,点击里面的SCI7,配置它工作于UART模式、选择引脚为P613、P614,如下图所示:

配置好引脚后,就需要配置UART本身了。点击Stack页面,点击“New Stack”然后找到“Connectivity > UART(r_sci_uart)”,添加新的Stack。如下图操作:

下一步要要配置新添加的r_sci_uart,如下图所示,先选中刚新建的r_sci_uart,点击“Properties”,然后设置:

  • 在“General”下,设置变量名称,比如“g_uart7”;选择对应的UART通道,比如把Channel设置为7,然后设置数据位(Data Bits)为8、检验位(Parity)为None、停止位(Stop Bits)为1。

  • 在“Baud”下,设置波特率(Baud Rate)为115200。

  • 在“Flow Control”下,不要修改默认配置,如果修改了需要按图改回去

  • 在“Interrupts”下,指定“Callback”为“uart7_callback”(这个函数需要我们实现)

  • 在“Pins”下,确认TXD7、TXD6分别为P613、P614,跟前面在“Pins”页面的设置保持一致。

配置好引脚和UART后,点击右上角的“Generate Project Content”就会生成代码。

7.1.2 配置信息解读

使用RASC配置UART后,生成了引脚配置信息、UART本身的配置信息。

  1. 引脚配置信息

该信息会在0701_UART\ra_gen\pin_data.c文件里生成。

在RASC里配置的每一个引脚,都会生成一个ioport_pin_cfg_t数组项,里面的内容跟配置时选择的参数一致。代码如下:

const ioport_pin_cfg_t g_bsp_pin_cfg_data[] = {{.pin = BSP_IO_PORT_06_PIN_13,.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN 
| (uint32_t) IOPORT_PERIPHERAL_SCI1_3_5_7_9)},{.pin = BSP_IO_PORT_06_PIN_14,.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN 
| (uint32_t) IOPORT_PERIPHERAL_SCI1_3_5_7_9)},
};
  • 第3~6行被用来配置P613;

  • 第7~10行被用来配置P614。它们都被配置为“IOPORT_CFG_PERIPHERAL_PIN”,也就是要连接到芯片的其它模块(非GPIO);

连接到哪个模块呢?另一个参数“IOPORT_PERIPHERAL_SCI1_3_5_7_9)”表示它们被连接到SCI模块。

  1. UART配置信息

该信息会在0701_UART\ra_gen\hal_data.c文件里生成。

在RASC里指定了UART使用哪个SCI通道、指定了它的数据格式(数据位/校验位/停止位)、指定了波特率等信息,这些配置信息都被放入一个uart_cfg_t结构体,部分代码摘录如下:

const uart_cfg_t g_uart7_cfg =
{.channel             = 7,.data_bits           = UART_DATA_BITS_8,.parity              = UART_PARITY_OFF,.stop_bits           = UART_STOP_BITS_1,.p_callback          = uart7_callback,......(省略内容)
}

结构体g_uart7_cfg里引用到了另一个结构体g_uart7_cfg_extend,里面含有时钟、FIFO、流量控制等信息,代码如下:

const sci_uart_extended_cfg_t g_uart7_cfg_extend =
{.clock                = SCI_UART_CLOCK_INT,.rx_edge_start          = SCI_UART_START_BIT_FALLING_EDGE,.noise_cancel         = SCI_UART_NOISE_CANCELLATION_DISABLE,.rx_fifo_trigger        = SCI_UART_RX_FIFO_TRIGGER_MAX,.p_baud_setting         = &g_uart7_baud_setting,.flow_control           = SCI_UART_FLOW_CONTROL_RTS,......(省略内容)
}
  • 第04~06行指定了数据格式,第07行指定了用户提供的回调函函数。

  • 第12~15行指定了时钟等硬件信息;

  • 还在第16行引用了另一个结构体g_uart7_baud_setting。在RASC里配置了波特率为115200,RASC会计算出各个寄存器的值,存放在结构体g_uart7_baud_setting里,代码如下:

    baud_setting_t    g_uart7_baud_setting =
    {/* Baud rate calculated with 0.469% error. */ .semr_baudrate_bits_b.abcse = 0, .semr_baudrate_bits_b.abcs = 0, .semr_baudrate_bits_b.bgdm = 1, .cks = 0, .brr = 53, .mddr = (uint8_t) 256, .semr_baudrate_bits_b.brme = false
    };
    

7.1.3 API接口

在0701_UART\ra\fsp\inc\api\r_uart_api.h中定义了uart模块的接口,它定义了一个结构体类型uart_api_t,内容如下:

typedef struct st_uart_api
{fsp_err_t (* open)(uart_ctrl_t * const p_ctrl, uart_cfg_t const * const p_cfg);fsp_err_t (* read)(uart_ctrl_t * const p_ctrl, uint8_t * const p_dest, uint32_t const bytes);fsp_err_t (* write)(uart_ctrl_t * const p_ctrl, uint8_t const * const p_src, uint32_t const bytes);fsp_err_t (* baudSet)(uart_ctrl_t * const p_ctrl, void const * const p_baudrate_info);fsp_err_t (* infoGet)(uart_ctrl_t * const p_ctrl, uart_info_t * const p_info);fsp_err_t (* communicationAbort)(uart_ctrl_t * const p_ctrl, uart_dir_t communication_to_abort);fsp_err_t (* callbackSet)(uart_ctrl_t * const p_api_ctrl, void (* p_callback)(uart_callback_args_t *),void const * const p_context, uart_callback_args_t * const p_callback_memory);fsp_err_t (* close)(uart_ctrl_t * const p_ctrl);fsp_err_t (* readStop)(uart_ctrl_t * const p_ctrl, uint32_t * remaining_bytes);
} uart_api_t;

在具体的C文件中,需要实现一个uart_api_t结构体,比如在r_sci_uart.c里实现了如下结构体:

/* UART on SCI HAL API mapping for UART interface */
const uart_api_t g_uart_on_sci =
{.open        = R_SCI_UART_Open,.close        = R_SCI_UART_Close,.write        = R_SCI_UART_Write,.read        = R_SCI_UART_Read,.infoGet       = R_SCI_UART_InfoGet,.baudSet       = R_SCI_UART_BaudSet,.communicationAbort = R_SCI_UART_Abort,.callbackSet     = R_SCI_UART_CallbackSet,.readStop      = R_SCI_UART_ReadStop,
};

要使用UART收发数据时,可以调用结构体g_uart_on_sci里的各个函数指针,也可以直接调用r_sci_uart.c里实现的各个函数(比如R_SCI_UART_Open、R_SCI_UART_Read)。

7.1.4 中断回调函数

操作一个UART引脚时,要先打开它(open),open函数会配置UART;然后再调用read、write函数读、写串口。需要注意的是,sci_uart模块里的读、写函数,只是“启动”读、写功能,这些函数返回时并不表示读、写已经完成。后续的读、写操作是由中断函数实现的。在传输过程中,中断函数会调用回调函数来处理各种状态(传输完成?出错?)。

回调函数原型在0701_UART\ra_gen\hal_data.h中定义,如下:

void uart7_callback(uart_callback_args_t * p_args);

它的p_args参数是uart_callback_args_t结构体类型,如下:

typedef struct st_uart_callback_arg
{uint32_t     channel;              /// 哪一个UART通道uart_event_t event;                /// event,什么原因/** 当event是UART_EVENT_RX_CHAR或UART_EVENT_ERR_PARITY时,data为刚接收到的字符* 当event是UART_EVENT_ERR_FRAMING或UART_EVENT_ERR_OVERFLOW时,data未被使用 */uint32_t     data;void const * p_context;            ///< Context provided to user during callback
} uart_callback_args_t;

回调函数被调用时,参数里的event表示原因,它是一个uart_event_t枚举类型,取值如下:

/** UART Event codes */
typedef enum e_sf_event
{UART_EVENT_RX_COMPLETE   = (1UL << 0), /// 使用read函数启动接收后,接收完毕UART_EVENT_TX_COMPLETE   = (1UL << 1), /// 发送完毕UART_EVENT_RX_CHAR       = (1UL << 2), /// 未调用read启动接收,但是接收到了数据UART_EVENT_ERR_PARITY    = (1UL << 3), /// 校验错误UART_EVENT_ERR_FRAMING   = (1UL << 4), /// < Mode fault error eventUART_EVENT_ERR_OVERFLOW  = (1UL << 5), /// FIFO溢出UART_EVENT_BREAK_DETECT  = (1UL << 6), /// < Break detect error eventUART_EVENT_TX_DATA_EMPTY = (1UL << 7), /// 最后一个字节已经发送出去了,但是还没发送完毕
} uart_event_t;
  • 对于接收,有2个event:UART_EVENT_RX_COMPLETE、UART_EVENT_RX_CHAR。前者被调用的时机是:调用read后接收到所有数据时;后者被调用的时机是:未调用read函数,但是接收到了数据。
  • 对于发送,有2个event:UART_EVENT_TX_DATA_EMPTY、UART_EVENT_TX_COMPLETE。以发送2个字节的数据为例,前者被调用的时机是:第1个字节已经成功发送完毕,第2个字节已经被移入发送器但是还没发送完毕;后者被调用的时机是:这2个字节都发送完毕。前者先被调用,在“最后一个字节被发送,但是未发送完毕”时被调用;后者在“最后一个字节也完全发送完毕后”被调用。

对于普通的读写操作,可以在回调函数里设置状态标记,用来表示读、写是否已经完成。这样,使用read、write函数启动读、写操作后,就可以轮询这些状态以等待操作完成。示例代码在0701_UART\src\hal_entry.c文件中,如下:

static volatile int g_uart7_tx_complete = 0;
static volatile int g_uart7_rx_complete = 0;void uart7_callback(uart_callback_args_t * p_args)
{switch (p_args->event){case UART_EVENT_TX_COMPLETE:{g_uart7_tx_complete  = 1;break;}case UART_EVENT_RX_COMPLETE:{g_uart7_rx_complete = 1;break;}default:{break;}}
}void uart7_wait_for_tx(void)
{while (!g_uart7_tx_complete);g_uart7_tx_complete = 0;
}
void uart7_wait_for_rx(void)
{while (!g_uart7_rx_complete);g_uart7_rx_complete = 0;
}
  • 第8~12行,在回调函数里设置全局变量g_uart7_tx_complete,表示发送完毕。用户程序在调用write启动UART发送后,就可以调用第25行的uart7_wait_for_tx以等待发送完毕。
  • 第13~17行,在回调函数里设置全局变量g_uart7_rx_complete,表示接收完毕。用户程序在调用read启动UART接收后,就可以调用第31行的uart7_wait_for_rx以等待接收完毕。

7.1.5 API接口用法

sci_uart模块的接口在文件0701_UART\ra\fsp\inc\api\r_uart_api.h中定义。

  1. 打开UART设备

此函数用于配置UART,并且标记转态为“已经打开”。函数原型:

fsp_err_t (* open)(uart_ctrl_t * const p_ctrl, uart_cfg_t const * const p_cfg);

此函数指针有两个参数,p_ctrl是一个uart_ctrl_t指针,它的定义如下:

typedef void uart_ctrl_t;

所以在r_uart_api.h文件里,p_ctrl实际上是一个void指针,它可以指向任意类型的数据类型,这是一种良好的编程思想:封装内部实现的细节。在r_sci_uart.h里,这个参数实际的类型是sci_uart_instance_ctrl_t结构体,定义如下:

/** UART instance control block. */
typedef struct st_sci_uart_instance_ctrl
{/* Parameters to control UART peripheral device */uint8_t  fifo_depth;               // FIFO深度uint8_t  rx_transfer_in_progress;  // 1表示当前正在处理一个“接收传输”……uint32_t open;                     // 表示状态,当前UART是否已经被打开、被配置……/* 发数据时的源数据buffer */uint8_t const * p_tx_src;/* 还有多少数据未发送 */uint32_t tx_src_bytes;/* 收数据时的目的buffer */uint8_t const * p_rx_dest;/* 还有多少数据未接收 */uint32_t rx_dest_bytes;/* 这个UART通道的寄存器基地址 */R_SCI0_Type * p_reg;void (* p_callback)(uart_callback_args_t *); // 回调函数……
} sci_uart_instance_ctrl_t;

结构体sci_uart_instance_ctrl_t的open成员被用来表示串口的状态,其他成员被用来实现读、写函数。比p_tx_src指向数据buffer,tx_src_bytes等于要发送的数据长度,每发送完n个数据中断函数里会把tx_src_bytes减去n,当中断函数发现tx_src_bytes等于0时,就会调用p_callback(它指向RASC里指定的回调函数)。

第二个参数p_cfg是一个uart_cfg_t结构体指针,在《7.1.2配置信息解读》里有详细描述。

  1. 关闭UART设备

关闭UART设备的函数指针是close,传入的参数是uart_ctrl_t结构体变量:

fsp_err_t (* close)(uart_ctrl_t * const p_ctrl);

这个函数指针在使用FSP生成到工程中会指向R_SCI_UART_Close,代码如下:

fsp_err_t R_SCI_UART_Close (uart_ctrl_t * const p_api_ctrl)
{sci_uart_instance_ctrl_t * p_ctrl = (sci_uart_instance_ctrl_t *) p_api_ctrl;……/* 修改状态位0,表示已经关闭 */p_ctrl->open = 0U;/* 禁止UART通道: 关闭中断、关闭时钟等 */p_ctrl->p_reg->SCR = 0U;……/* 禁止中断 */R_BSP_IrqDisable(p_ctrl->p_cfg->rxi_irq);R_BSP_IrqDisable(p_ctrl->p_cfg->eri_irq);/* 关闭这个UART通道的电源 */R_BSP_MODULE_STOP(FSP_IP_SCI, p_ctrl->p_cfg->channel);……;return FSP_SUCCESS;
}
  1. 发送数据

使用write函数来“启动”发送,函数返回时并不表示数据已经全部发送完毕。原型如下:

fsp_err_t (* write)(uart_ctrl_t * const p_ctrl, uint8_t const * const p_src, uint32_t const bytes);
  1. 读取数据

使用read函数来“启动”接收,函数返回时并不表示数据已经全部接收完毕。原型如下:

fsp_err_t (* read)(uart_ctrl_t * const p_ctrl,uint8_t * const p_dest, uint32_t const bytes);

如果在调用read函数来“启动接收”之前,UART已经接收到了数据,中断函数也会调用回调函数(event为UART_EVENT_RX_CHAR)。

7.2 UART读写实验

本实验的源码是“0701_UART”,使用串口工具给开发板发送字符,开发板接收到后把字符加1,再发给PC。

7.2.1 配置UART

参考《7.1.1使用RASC配置》进行配置,参考《7.1.4中断回调函数》添加代码。

7.2.2 应用程序

在hal_entry.c文件中的hal_entry()函数里添加如下代码:

/* TODO: add your own code here */
fsp_err_t err;
uint8_t c;/* 配置串口 */
err = g_uart7.p_api->open(g_uart7.p_ctrl, g_uart7.p_cfg);
/* 启动发送字符 */
g_uart7.p_api->write(g_uart7.p_ctrl, "100ask\r\n", 8);
/* 等待发送完毕 */
uart7_wait_for_tx();
while (1)
{/* 启动接收字符 */err = g_uart7.p_api->read(g_uart7.p_ctrl, &c, 1);if (!err){/* 等待接收完毕 */uart7_wait_for_rx();c++; /* 加1 *//* 启动发送字符 */g_uart7.p_api->write(g_uart7.p_ctrl, &c, 1);/* 等待发送完毕 */uart7_wait_for_tx();}
}

7.2.3 上机实验

先使用串口工具打开开发板自带的USB串口,波特率设置为115200,数据位为8,停止位为1,无校验位,不使用流控。

程序运行后,在串口工具里可以看到打印出“100ask”,然后输入“1234abc”会显示出“2345bcd”,示例如下:

100ask
2345bcd

7.3 stdio实验

本实验的源码是“0702_stdio”,它的功能是:使用printf、scanf。

7.3.1 实现思路

printf、scanf本身的代码来自于C库,或者MicroLIB库。要在串口上使用printf、scanf,实现串口相关的fputc、fgetc函数即可。

在使用scanf时,使用“回显功能”体验更好。“回显”就是:在PC机上使用串口工具发送一个字符给开发板,开发板接收到后把同样的字符发回给PC机,这样串口工具就会显示出这个字符。如果不使用回显功能,我们在串口工具上输入时没有任何显示,相当于“盲打”。

如何实现“回显”?在fgetc函数里,接收到PC发来的字符后,再使用fputc把它发送给PC即可。如果发现接收到回车字符(‘\r’),则还需要发送换行字符(’\n’),否则我们在串口工具上按下回车键时,光标只会回到行首而不会调到下一行。

7.3.2 编写程序

“0702_stdio”是在“0701_UART”的基础上修改的。在src\hal_entry.c中实现了fputc、fgetc函数,代码如下:

/* 重写这个函数,重定向printf */

int fputc(int ch, FILE * f)
{(void)f;/* 启动发送字符 */g_uart7.p_api->write(g_uart7.p_ctrl, (uint8_t const * const)&ch, 1);/* 等待发送完毕 */uart7_wait_for_tx();return ch;
}/* 重写这个函数,重定向scanf */
int fgetc(FILE *f)
{uint8_t ch;(void)f;/* 启动接收字符 */g_uart7.p_api->read(g_uart7.p_ctrl, &ch, 1);/* 等待接收完毕 */uart7_wait_for_rx();/* 回显 */{fputc(ch, NULL);/* 回车之后加换行 */if (ch == '\r'){fputc('\n', NULL);;}}return (int)ch;
}

示例代码如下,从串口上读入两个整数,求和后输出:

void hal_entry(void)
{/* TODO: add your own code here */fsp_err_t err;uint32_t a, b;/* 配置串口 */err = g_uart7.p_api->open(g_uart7.p_ctrl, g_uart7.p_cfg);printf("www.100ask.net\r\n");while (1){printf("Please enter two number:\r\n");scanf("%d%d", &a, &b);printf("%d+%d=%d\r\n", a, b, a+b);}
}

7.3.3 上机实验

在Keil工程里,确保选择了MicroLIB,如下图确认:

程序烧写、运行后,在串口工具上看到“Please enter two number:”时,输入一个数值并回车,再输入第2个数值并回车,即可看到效果。如下图:

www.100ask.net
Please enter two number:
12
34
12+34=46
Please enter two number:

7.4 接收大量数据

本实验的源码是“0703_circle_buffer”,它的能接收大量的串口数据。板子使用串口连接其他模块(比如WIFI模块)时,这些模块有可能突然发来大量的数据。怎样确保数据不丢失?

7.4.1 问题与解决思路

使用sci_uart模块接收串口数据时,有两种方法:

  1. 程序调用read函数“启动接收”,中断回调函数里确定参数event等于UART_EVENT_RX_COMPLETE,表示接收到指定数目的数据了
  2. 程序没有调用read函数,中断回调函数确定参数event等于UART_EVENT_RX_CHAR,然后保存数据

假设函数A读取串口数据、处理,示例代码如下:

void A(void)
{while (1){/* 启动接收字符 */err = g_uart7.p_api->read(g_uart7.p_ctrl, &c, 1);/* 等待接收完毕 */uart7_wait_for_rx();/* 处理 */process_data();}
}

考虑这个场景:

  • 第6行代码启动“接收”,在第8行等待接收成功
  • 第10行的process_data函数非常耗时
  • 在第10行的函数执行期间,外接的模块继续发来大量的数据,这个时候就会出现问题

中断程序得到其他字符时会调用回调函数:event为UART_EVENT_RX_CHAR,而不是UART_EVENT_RX_COMPLETE:因为process_data未执行完,导致第6行的代码还没有被调用。

所以,我们需要在回调函数里处理event为UART_EVENT_RX_CHAR的情况:以存储“来不及处理的字符”。既然如此,干脆把所有接收到的字符存入一个buffer,同时修改fgetc让它直接返回buffer里的数据。

对于这个buffer,中断函数的回调函数从UART得到数据后写入buffer,程序从中读取数据,非常适合使用环形缓冲区。

7.4.2 环形缓冲区

环形缓冲区特别适合这种场景:

  • 一方写buffer
  • 另一方读buffer。

环形缓冲区实际上还是一维数组,假设有N个数组项,从第0个数组项开始遍历,访问完第N-1个数组项后,再从0开始——这就是“环形”的含义,如下图所示:

环形缓冲区的工作原理如下图所示:

① 有读位置、写位置:r、w,它们表示“下一个要读的位置”、“下一个要写的位置”。初始值都是0。
② 写数据时:把数据写入buffer[w],然后调整w指向下一个位置(当w越界后要从0开始)。
③ 读数据时:从buffer[r]读出数据,然后调整r指向下一个位置(当r越界后要从0开始)。
④ 判断buffer为空:r等于w时表示空
⑤ 判断buffer为满:“下一个写位置”等于当前读位置

7.4.3 环形缓冲区编程

以面向对象的编程思想,针对环形缓冲区先定义一个结构体类型,代码在0703_circle_buffer\src\circle_buf.h文件中,如下:

typedef struct circle_buf {uint32_t r;uint32_t w;uint32_t max_len;uint8_t  *buffer;    int32_t (*put)(struct circle_buf *pcb, uint8_t v);int32_t (*get)(struct circle_buf *pcb, uint8_t *pv);
}circle_buf_t;
  • 第2~5行分别是读位置、写位置、buffer大小、buffer指针。
  • 第6、7行是两个函数指针:写buffer、读buffer。

在0703_circle_buffer\src\circle_buf.c中,定义并初始化了一个环形缓冲区结构体,代码如下:

static uint8_t rx_buf[64];
circle_buf_t g_rx_buf;void circlebuf_init(void)
{g_rx_buf.r = g_rx_buf.w = 0;g_rx_buf.buffer  = rx_buf;g_rx_buf.max_len = sizeof(rx_buf);g_rx_buf.get = circlebuf_get;g_rx_buf.put = circlebuf_put;
}
  • 第01行定义了一个buffer,在第50行里它被赋给环形缓冲区结构体。
  • 第02行定义了一个全局的环形缓冲区结构体变量g_rx_buf,以后会直接使用这个变量。
  • 第04行的circlebuf_init函数被用来初始化g_rx_buf。
  • 第09、10行给g_rx_buf设置了两个函数,这是环形缓冲区的核心操作函数。
  1. 写环形缓冲区的代码

    static int32_t circlebuf_put(struct circle_buf *pcb, uint8_t v)
    {uint32_t next_w;/* 计算"下一个写位置的下一个", 如果越界就要设置为0 */next_w = pcb->w + 1;if (next_w == pcb->max_len)next_w = 0;/* 未满? */if (next_w != pcb->r){/* 写入数据 */pcb->buffer[pcb->w] = v;/* 设置下一个写位置 */pcb->w = next_w;return 0;}return -1;
    }
    
  2. 读环形缓冲区的代码

     static int32_t circlebuf_get(struct circle_buf *pcb, uint8_t *pv){/* 不空? */if (pcb->w != pcb->r){/* 读出数据 */*pv = pcb->buffer[pcb->r];/* 计算"下一个读位置", 如果越界就要设置为0 */pcb->r++;if (pcb->r == pcb->max_len)pcb->r = 0;return 0;}return -1;}
    

7.4.4 改造程序

使用环形缓冲区后,应用程序无需调用UART的read函数来“启动读取”,只需要读取环形缓冲区即可。

谁给环形缓冲区提供数据?板子接收到串口数据时,中断函数被调用,中断函数会调用回调函数,回调函数里把数据写入环形缓冲区。

在0703_circle_buffer\src\hal_entry.c里,回调函数代码如下:

static volatile int g_uart7_tx_complete = 0;void uart7_callback(uart_callback_args_t * p_args)
{switch (p_args->event){case UART_EVENT_TX_COMPLETE:{g_uart7_tx_complete  = 1;break;}case UART_EVENT_RX_CHAR:{g_rx_buf.put(&g_rx_buf, (uint8_t)p_args->data);break;}default:{break;}}
}
  • 第12~16行,把接收到在字符放入环形缓冲区g_rx_buf里。

现在可以修改fgetc函数了,代码在0703_circle_buffer\src\console.c里,如下:

/* 重写这个函数,重定向scanf */
int fgetc(FILE *f)
{uint8_t ch;(void)f;while (g_rx_buf.get(&g_rx_buf, &ch)){};/* 回显 */{fputc(ch, NULL);/* 回车之后加换行 */if (ch == '\r'){fputc('\n', NULL);;}}return (int)ch;
}
  • 第07行从环形缓冲区中得到数据:如果失败则再次读取。
  • 第09~18行是回显操作。

为了测试“唤醒缓冲区可以不丢数据”,修改应用程序代码在0703_circle_buffer\src\hal_entry.c,如下:

void hal_entry(void)
{/* TODO: add your own code here */fsp_err_t err;int a = 1, b =2 ;/* 初始化环形缓冲区 */circlebuf_init();/* 配置串口 */err = g_uart7.p_api->open(g_uart7.p_ctrl, g_uart7.p_cfg);printf("www.100ask.net\r\n");printf("waiting 10 seconds, you can enter datas too.\r\n");// 延时10秒R_BSP_SoftwareDelay(1, 10*BSP_DELAY_UNITS_SECONDS);while (1){printf("Please enter two number:\r\n");scanf("%d%d", &a, &b);printf("%d + %d = %d\r\n", a, b, a+b);}
}
  • 第16行里故意延时10秒,你可以在这个时间里输入字符。

7.4.5 上机实验

当程序打印出“waiting 10 seconds, you can enter datas too.”时,可以输入1并回车、输入2并回车(盲打,这时没有回显),然后等待。最后可以看到打印后续结果:

www.100ask.net
waiting 10 seconds, you can enter datas too.
Please enter two number:
1
2
1 + 2 = 3
Please enter two numbe

本章完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/106633.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java学习笔记——34多线程01

多线程 实现多线程进程和线程的区别多线程的实现方式方式一&#xff1a;继承Thread类设置线程名称线程调度线程控制线程生命周期 方式二&#xff1a;实现Runnable接口 实现多线程 进程和线程的区别 进程&#xff1a;是正在运行的程序 是系统进行资源分配和调用的独立单位每一…

华为云云耀云服务器L实例评测|部署spring项目端口开放问题的解决 服务器项目环境搭建MySQL,Redis,Minio...指南

目录 引出书接上回&#xff0c;部署spring项目&#xff0c;端口访问失败最后排查结果反馈 尝试的几种解决方案【未成功】1.指定tomcat启动ipv4端口2.添加开放端口规则保存规则防火墙相关命令记录 最终成功解决【成功&#xff01;】用firewall成功了问题来了&#xff0c;如果这里…

一个好玩的浏览器插件

背景 最近抽空开发了一个有意思的浏览器插件。背景是我们在开发过程中有时需要做一些测试验证&#xff0c;需要修改请求头字段和响应头字段的内容&#xff0c;有时需要在页面做测试&#xff0c;反复请求同一个接口&#xff0c;并修改一些字段。 如果此时使用nginx做代理转发再…

机器学习——自然语言处理(NLP)一

机器学习——自然语言处理&#xff08;NLP&#xff09;一 文章目录 前言一、TF-IDF算法1.1. 原理1.2. 算法步骤&#xff1a;1.2.1. 文本预处理1.2.2. 构建词袋模型1.2.3. 计算TF-IDF值1.2.4. 特征选择 1.3. 代码实现1.3.1. TF-IDF1.3.2 计数器向量化文本1.3.3. 两者的区别1.3.4…

stm32---基本定时器(TIM6,TIM7)

STM32F1的定时器非常多&#xff0c;由两个基本定时器&#xff08;TIM6&#xff0c;TIM7&#xff09;、4个通用定时器&#xff08;TIM2-TIM5&#xff09;和两个高级定时器&#xff08;TIM&#xff11;&#xff0c;TIM&#xff18;&#xff09;组成。基本定时器的功能最为简单&am…

嵌入式Linux驱动开发(同步与互斥专题)(一)

一、内联汇编 1.1、语法 内联汇编实现加法 1.2、同步互斥失败的例子 进程A在读出valid时发现它是1&#xff0c;减1后为0&#xff0c;这时if不成立&#xff1b;但是修改后的值尚未写回内存&#xff1b;假设这时被程序B抢占&#xff0c;程序B读出valid仍为1&#xff0c;减1后为…

Newman+Jenkins实现接口自动化测试

一、是什么Newman Newman就是纽曼手机这个经典牌子&#xff0c;哈哈&#xff0c;开玩笑啦。。。别当真&#xff0c;简单地说Newman就是命令行版的Postman&#xff0c;查看官网地址。 Newman可以使用Postman导出的collection文件直接在命令行运行&#xff0c;把Postman界面化运…

tkinter树形图组件

文章目录 初步回调函数绑定滚动条 初步 Treeview是ttk中的树形表组件&#xff0c;功能十分强大&#xff0c;非常适用于系统路径的表达。为了知道属性图到底是什么&#xff0c;下面先做个最简单的树形图 其代码如下 import tkinter as tk from tkinter import ttkdct {"…

【Proteus仿真】【STM32单片机】安全驾驶检测系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 系统运行后&#xff0c;LCD1602显示传感器检测的酒精浓度和CO2值&#xff0c;以及阈值&#xff1b;若按下K3键进入阈值设置模式&#xff0c;默认以酒精阈值开始位置闪烁&#xff1b;再次按下K3键则进入CO…

无涯教程-JavaScript - NPV函数

描述 NPV函数通过使用折现率以及一系列未来付款(负值)和收入(正值)来计算投资的净现值。 语法 NPV (rate,value1,[value2],...)争论 Argument描述Required/OptionalRateThe rate of discount over the length of one period.RequiredValue11 to 254 arguments representing…

UDP的可靠性传输

UDP系列文章目录 第一章 UDP的可靠性传输-理论篇&#xff08;一&#xff09; 第二章 UDP的可靠性传输-理论篇&#xff08;二&#xff09; 文章目录 UDP系列文章目录前言1.TCP 和UDP格式对比2.UDP分片原理3.UDP 传输层应该注意问题4.MTU5.UDP 分片机制设计重点 一、ARQ协议什么…

2023年中国电影行业研究报告

第一章 行业概况 1.1 定义 电影行业是一门涉及电影制作、发行、放映和推广的综合艺术和商业活动。它结合了戏剧、音乐、舞蹈、绘画等多种艺术形式&#xff0c;通过视觉和听觉的方式向观众展示故事和情感。 电影不仅仅是一门艺术&#xff0c;更是一项复杂的商业运作。它涵盖了…