瑞萨单片机william hill官网
直播中

hehung

8年用户 659经验值
擅长:嵌入式技术
私信 关注

【瑞萨RA4系列开发板体验】3. KEIL下UART实现printf与scanf重定向

之前发帖:

【瑞萨RA4系列开发板体验】1. 新建工程+按键控制LED

【瑞萨RA4系列开发板体验】2. KEIL环境搭建+STLINK调试+FreeRTOS使用

前言

MDK支持串口对prinf以及scanf的重映射,这样我们就可以使用C原因的标准库的printf以及scanf函数实现串口打印数据以及接收数据了,使用起来超级方便,本文讲解如何在MDK下实现瑞萨RA4M2的printf与scanf的重映射。

本文实现如下功能:

  1. 配置uart9作为重映射端口;
  2. 使用scanf结束上位机发来的数据:open,close命令;
  3. 接收open,打开LED1,使用printf输出LED1 open;
  4. 接收到close,关闭LED1,使用printf输出LED1 close;
  5. 接收到其他命令,输出Unknow command。

硬件威廉希尔官方网站 分析

见下图,板载的USB串口连接:

TX - P109

RX - P110

2.png

UART配置

串口配置使用瑞萨提供的工具RASC,配置流程如下:

  1. 打开RASC,如下图,直接到Tools下打开即可,前提是已经将RASC工具添加了,没添加的话请参考我上一篇帖子。

    1.png

  2. 修改debug引脚配置

    因为JTAG吧串口引脚占用了,而且我也不需要用JTAG接口,我是用SWD接口进行调试,所以修改一下配置,让串口引脚P109与P110能够作为串口使用,如果使用JTAG接口的话可能会冲突。

    3.png

修改之后,变成如下状态:

4.png

  1. 配置SCI

    P109以及P110使用的是uart9,即SCI9,见下图引脚复用关系:

    8.png

在Pins配置界面下:配置SCI9为异步串口或者同步串口,异步串口使用中断方式,我决定使用异步传输,选择好了异步传输之后,会自动关联到P109以及P110。

5.png

  1. 配置串口功能

    切换到Stacks配置界面:添加串口,如下图所示:

    6.png

修改串口名字;

修改串口Channel为9,因为为uart9

添加串口中断回调函数为uart9_notification,可以根据实际情况修改串口中断的优先级,我这里没做修改,使用的默认值。

7.png

通过上述步骤,串口配置完成,点击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';

串口初始化

// Uart initialize
void Uart_Init(void)
{
	fsp_err_t err = FSP_SUCCESS;
	
	/* Open the transfer instance with initial configuration. */
    err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
    assert(FSP_SUCCESS == err);
}

编写串口回调函数

实现逻辑:

  1. 当存在发送完成事件时,置位发送完成标志;
  2. 没接收一个字符,置位接收完成标志,并将接收的字符保存在变量uart_recv_char里;
// callback function for uart9
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是一个字符一个字符的发送,每次只需要处理一个字符即可。

实现逻辑:

  1. 发送一个字符,如果发送失败,进入异常;
  2. 等待中断发送完成;
  3. 清空发送完成标志。

fgets是一个字符一个字符的接收,每次只需要处理一个字符即可。

实现逻辑:

  1. 等待接收一个字符标志;
  2. 清空字符接收标志;
  3. 返回接收到的字符。
// fputs for printf or other print function in standard
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();
	/* Waiting until transmit finished */
	while(uart_send_complete_flag == false) 
	{}
	uart_send_complete_flag = false;
	
    return ch;
}

// fgets for scanf or other input function in standard
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)
{
	/* Initialize the uart for implement the 'printf' and 'scanf' */
	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
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

效果

写了一个测试用例来验证printf以及scanf功能:

  1. 上位机发送open,LED1亮,并返回LED1 open;
  2. 上位机发送了close,LED灭,并返回LED1 close;
  3. 上位机发送了其他内容,返回Uknow command。
    9.png

更多回帖

发帖
×
20
完善资料,
赚取积分