之前发帖:
【瑞萨RA4系列开发板体验】1. 新建工程+按键控制LED
【瑞萨RA4系列开发板体验】2. KEIL环境搭建+STLINK调试+FreeRTOS使用
前言
MDK支持串口对prinf以及scanf的重映射,这样我们就可以使用C原因的标准库的printf以及scanf函数实现串口打印数据以及接收数据了,使用起来超级方便,本文讲解如何在MDK下实现瑞萨RA4M2的printf与scanf的重映射。
本文实现如下功能:
- 配置uart9作为重映射端口;
- 使用scanf结束上位机发来的数据:open,close命令;
- 接收open,打开LED1,使用printf输出LED1 open;
- 接收到close,关闭LED1,使用printf输出LED1 close;
- 接收到其他命令,输出Unknow command。
硬件电路分析
见下图,板载的USB串口连接:
TX - P109
RX - P110
UART配置
串口配置使用瑞萨提供的工具RASC,配置流程如下:
-
打开RASC,如下图,直接到Tools下打开即可,前提是已经将RASC工具添加了,没添加的话请参考我上一篇帖子。
-
修改debug引脚配置
因为JTAG吧串口引脚占用了,而且我也不需要用JTAG接口,我是用SWD接口进行调试,所以修改一下配置,让串口引脚P109与P110能够作为串口使用,如果使用JTAG接口的话可能会冲突。
修改之后,变成如下状态:
-
配置SCI
P109以及P110使用的是uart9,即SCI9,见下图引脚复用关系:
在Pins配置界面下:配置SCI9为异步串口或者同步串口,异步串口使用中断方式,我决定使用异步传输,选择好了异步传输之后,会自动关联到P109以及P110。
-
配置串口功能
切换到Stacks配置界面:添加串口,如下图所示:
修改串口名字;
修改串口Channel为9,因为为uart9
添加串口中断回调函数为uart9_notification,可以根据实际情况修改串口中断的优先级,我这里没做修改,使用的默认值。
通过上述步骤,串口配置完成,点击Generate Project Content生成配置代码。
代码实现
要在MDK上实现串口的printf以及scanf工功能,只需要实现fputs以及fgets函数即可,详细实现过程如下:
头文件
#include "hal_data.h"
#include "stdio.h"
全局变量
volatile bool uart_send_complete_flag = false;
volatile bool uart_recv_complete_flag = false;
volatile uint32_t uart_recv_char = '\0';
串口初始化
void Uart_Init(void)
{
fsp_err_t err = FSP_SUCCESS;
err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
assert(FSP_SUCCESS == err);
}
编写串口回调函数
实现逻辑:
- 当存在发送完成事件时,置位发送完成标志;
- 没接收一个字符,置位接收完成标志,并将接收的字符保存在变量uart_recv_char里;
void uart9_notification(uart_callback_args_t * p_args)
{
if (p_args->event == UART_EVENT_TX_COMPLETE)
{
uart_send_complete_flag = true;
}
else if (p_args->event == UART_EVENT_RX_CHAR)
{
uart_recv_char = p_args->data;
uart_recv_complete_flag = true;
}
}
实现fputs以及fgets
fputs是一个字符一个字符的发送,每次只需要处理一个字符即可。
实现逻辑:
- 发送一个字符,如果发送失败,进入异常;
- 等待中断发送完成;
- 清空发送完成标志。
fgets是一个字符一个字符的接收,每次只需要处理一个字符即可。
实现逻辑:
- 等待接收一个字符标志;
- 清空字符接收标志;
- 返回接收到的字符。
int fputc(int ch, FILE *f)
{
fsp_err_t err = FSP_SUCCESS;
(void)f;
err = R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);
if(FSP_SUCCESS != err)
__BKPT();
while(uart_send_complete_flag == false)
{}
uart_send_complete_flag = false;
return ch;
}
int fgetc(FILE *f)
{
(void)f;
while (uart_recv_complete_flag == false)
{}
uart_recv_complete_flag = false;
return (int)uart_recv_char;
}
演示代码实现
实现逻辑:
scanf等待串口接受数据,当接收的数据是open时,printf打印LED1 open并且控制LED1亮;当接收的数据是close时,printf打印LED1 close并且控制LED1灭;当接收的数据没定义时,printf打印Unknow command。
注意:串口上位机发送字符的时候,后面需要跟上空格,表示字符串结束,不然scanf不能正常接收字符串。
void hal_entry(void)
{
Uart_Init();
printf ("printf and scanf test!!\n");
while (1)
{
char recv_char[10];
scanf("%s", recv_char);
if (strcmp(recv_char, "open") == 0)
{
printf ("LED1 open\n");
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_15, BSP_IO_LEVEL_HIGH);
}
else if (strcmp(recv_char, "close") == 0)
{
printf ("LED1 close\n");
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_15, BSP_IO_LEVEL_LOW);
}
else
{
printf ("Unknow command\n");
}
}
#if BSP_TZ_SECURE_BUILD
R_BSP_NonSecureEnter();
#endif
}
效果
写了一个测试用例来验证printf以及scanf功能:
- 上位机发送open,LED1亮,并返回LED1 open;
- 上位机发送了close,LED灭,并返回LED1 close;
- 上位机发送了其他内容,返回Uknow command。