IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> freertos学习03-cli终端移植及调试 -> 正文阅读

[嵌入式]freertos学习03-cli终端移植及调试

1、前言

接触过Linux的都知道,最好用的一个工具就是终端,终端可以输入命令运行代码,查看信息,非常便捷。而搞单片机开发的好像基本没怎么接触过类似的工具,开发单片机用的最多的就是使用串口打印工具进行调试printf,但这个缺陷是,只能做显示,不能进行交互,如果需要显示新的信息,需要改代码然后编译,烧写,运行。这样操作难度比较大,比如在客户现场售后维护同时要进行调试,这个操作基本不可行,一般售后人员没有编写代码的能力或者权限。

为了方便代码调试以及bug排除。最好是能有一个类似linux上的串口中断,我可以输入命令,终端给我反馈数据。之前有用过原子哥写的usmart工具,很简洁,可以实现中断运行函数。这段时间在研究freertos,发现其中就包含了这样的组件,在freerots中叫做cli。

经过了初步研究,移植后。运行效果如下。


Command not recognised.  Enter 'help' to view a list of available commands.


[Press ENTER to execute the previous command again]
>helo
Command not recognised.  Enter 'help' to view a list of available commands.


[Press ENTER to execute the previous command again]
>help

help:
 Lists all the registered commands


task-stats:
 Displays a table showing the state of each FreeRTOS task

echo-3-parameters <param1> <param2> <param3>:
 Expects three parameters, echos each in turn

echo-parameters <...>:
 Take variable number of parameters, echos each in turn

hello:
 Displays a strings hello world on the terminal

led:
 control led on and off

[Press ENTER to execute the previous command again]
>hello
 hello world

[Press ENTER to execute the previous command again]
>led on
led on
1: on

[Press ENTER to execute the previous command again]
>led off
led off
1: off

[Press ENTER to execute the previous command again]
>task-stats
Task              State   Priority  Stack    #
************************************************
CLI             X       1       446     1
IDLE            R       0       118     3
Tmr Svc         B       31      228     4
tx_task         B       1       100     5

[Press ENTER to execute the previous command again]
>

输入hello+回车,会打印hello world。led on+回车会点亮一个led,led off+回车会关闭一个led。task-stats可以打印所有任务的状态。输入help+回车可以显示所有可运行的命令。这个效果还是不错的,后续如果用到了文件系统,还可以实现 ls mkdir touch这些Linux常用命令。可拓展性还是比较强的,下面就讲下如何移植到st或gd32,我用的是gd32f107,。具体代码仓库连接:
https://gitee.com/yvany/gd_freertos.git

2、cli移植

2.1cli组件

首先要下载一个freertos源码。下载方式见freertos.org可以参考之前的文章。
在这里插入图片描述
在freertos-plus文件夹下的source文件下,可以找到cli组件,如上图。
在这里插入图片描述
打开文件夹,cli组件很简单,就一个.c一个.h文件。理论上来说,只要移植这两文件就好了。但是为了方便,可以移植一个官方已经准备好的demo。

在这里插入图片描述

在freertos-plus->demo->coomon->cli_demos文件下下可以看到上述4个文件。初次使用,我们需要的只是上图中红框中的两个文件,这两个一个示例,一个是在串口下实现的中断。

为了进一步简化代码工作量,可以直接使用官方的底层串口驱动文件。
在这里插入图片描述
至此,移植cli需要的文件就准备完毕了。下面只需要将这些放到工程cli文件夹目录下即可。
在这里插入图片描述
最终组件需要的文件如上图所示。剩下就是将这些文件添加到mdk keil工程中。

2.2 实现串口初始化及中断

在这里插入图片描述
打开mdk,添加cli 组,并添加文件。

在serial.c中实现串口初始化,以及中断函数。

xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
xComPortHandle xReturn;


	/* Create the queues used to hold Rx/Tx characters. */
	xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	xCharsForTx = xQueueCreate( uxQueueLength + 1, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	
	/* If the queue/semaphore was created correctly then setup the serial port
	hardware. */
	if( ( xRxedChars != serINVALID_QUEUE ) && ( xCharsForTx != serINVALID_QUEUE ) )
	{
		/* Enable USART1 clock */
	dma_parameter_struct dma_init_struct;  // DMA配置参数结构体 
 
	rcu_periph_clock_enable(RCU_GPIOD);  // 使能 GPIOD 时钟 
	rcu_periph_clock_enable(RCU_AF);
	rcu_periph_clock_enable(RCU_USART2);  // 使能 USART2 时钟
	rcu_periph_clock_enable(RCU_DMA0);   // 使能 DMA0 时钟 
	gpio_pin_remap_config(GPIO_USART2_FULL_REMAP, ENABLE);
 
	gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOD, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
 
	usart_deinit(USART2);
	usart_baudrate_set(USART2, 115200U);
	usart_word_length_set(USART2, USART_WL_8BIT);
	usart_stop_bit_set(USART2, USART_STB_1BIT);
	usart_parity_config(USART2, USART_PM_NONE);
	usart_hardware_flow_rts_config(USART2, USART_RTS_DISABLE);
	usart_hardware_flow_cts_config(USART2, USART_CTS_DISABLE);
	
	usart_receive_config(USART2, USART_RECEIVE_ENABLE);
	usart_transmit_config(USART2, USART_TRANSMIT_ENABLE);
	
	usart_interrupt_enable(USART2,USART_INT_IDLE);
	
	usart_enable(USART2);
 

    /* deinitialize DMA channel3(USART0 tx) */
    dma_deinit(DMA0, DMA_CH2);
    dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;
    dma_init_struct.memory_addr = (uint32_t)g_usart2_rx_buff;
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    dma_init_struct.number = sizeof(g_usart2_rx_buff);
    dma_init_struct.periph_addr = USATR2_DATA_ADDRESS;
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
    dma_init(DMA0, DMA_CH2, &dma_init_struct);
	
	dma_circulation_disable(DMA0, DMA_CH2);                   // 关闭DMA循环模式
	dma_memory_to_memory_disable(DMA0, DMA_CH2);              //  DMA内存到内存模式不开启 
	dma_channel_enable(DMA0, DMA_CH2);                        // 使能DMA传输 
	
	usart_dma_receive_config(USART2, USART_DENR_ENABLE); // USART0 DMA接收模式开启 
	nvic_irq_enable(USART2_IRQn, 5, 0);                  // USART中断设置,抢占优先级0,子优先级0 
	}
	else
	{
		xReturn = ( xComPortHandle ) 0;
	}

	/* This demo file only supports a single port but we have to return
	something to comply with the standard demo header file. */
	return xReturn;
}

这里我使用的串口接收dma配合串口空闲中断。移植时,将串口初始化代码填入上面的函数。
在这里插入图片描述
需要添加的在这个if里面,其他的不用动,保持原样。

void USART2_IRQHandler( void )
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
	uint16_t usart2_rx_len = 0;
	uint8_t data = 0;
	if (RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_IDLE)) //空闲中断
    {
		usart_interrupt_flag_clear(USART2, USART_INT_FLAG_IDLE);  // 清除空闲中断标志位
		data = (uint8_t)usart_data_receive(USART2);
				dma_channel_disable(DMA0, DMA_CH2);                     // 关闭DMA传输 
		
		usart2_rx_len = sizeof(g_usart2_rx_buff) - dma_transfer_number_get(DMA0, DMA_CH2);
		for(int i = 0; i<usart2_rx_len;i++)
		{
		  xQueueSendFromISR(xRxedChars, &g_usart2_rx_buff[i], &xHigherPriorityTaskWoken);	
		  portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );			
		
		}

		
		dma_memory_address_config(DMA0, DMA_CH2, (uint32_t)g_usart2_rx_buff);
		dma_transfer_number_config(DMA0, DMA_CH2, sizeof(g_usart2_rx_buff));
		dma_channel_enable(DMA0, DMA_CH2); // 开启DMA传输 

	}
	

}

添加串口中断函数,为了方便,我这里直接将其默认的名字改成串口中断函数名称。由于我使用的是串口dma空闲中断。所以需要将读到的值用队列一个字节一个字节的发送出去。

如果直接采用的串口接收中断。可以将上图的for循环去掉。直接将受到的一个字节发送出去。

2.3配置

在这里插入图片描述
在FreeRTOS_CLI.h文件下添加宏定义,定义中端最大输出的字符长度。

#define configCOMMAND_INT_MAX_OUTPUT_SIZE 1000

在这里插入图片描述
在串口控制台c文件中,注意上面的宏定义,这个定义了终端最大输入的字节数量。如果超了可能会存在硬件错误的问题。初步使用一定要注意。

最后就是在main函数中初始化cli组件
在这里插入图片描述
main函数中,添加如下初始化函数命令。定义cli任务堆栈大小为4k字节,优先级为1,这里堆栈是设置的比较大,实际项目中,要根据实际设置的小一点。

vRegisterSampleCLICommands( );
vUARTCommandConsoleStart(1024,1);

最后编译运行,下载烧录。

串口终端建议使用MobaXterm,完全免费,支持多种协议。

打开MobaXterm。新建串口终端。
在这里插入图片描述
最后输入回车
在这里插入图片描述
如果有输出内容如上图,则说明成功,这个时候,可以输入官方已经实现了的命令

task-stats

在这里插入图片描述
打印了当前系统的开销,可见cli的剩余堆栈用量还是很大的,可以设置的小一些。

至此初步的移植就完成了。

3、自定义cli命令

3.1实现hello命令

要求:终端输入hello;终端输出 hello world

先上代码,再解释原理。在Sample_CLI-commands.c中添加如下代码

//声明prvhelloCommand函数,注意函数参数要完全保持一致
static BaseType_t prvhelloCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );


1、声明hello命令函数,这个函数返回值及参数要与官方代码里的保持一致,不能修改。

//定义xhello对象。第一行终端识别的命令。第二行注释信息,第三行,执行函数,第四行,参数个数,这是没有参数所以是0
static const CLI_Command_Definition_t xhello =
{
	"hello", /* The command string to type. */
	"\r\nhello:\r\n Displays a strings hello world on the terminal \r\n",
	prvhelloCommand, /* The function to run. */
	0 /* No parameters are expected. */
};

2、定义hello对象,第一行终端识别的命令。第二行注释信息,第三行,执行函数,第四行,参数个数,这是没有参数所以是0

static BaseType_t prvhelloCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *const pcHeader = " hello world\r\n";
BaseType_t xSpacePadding;

	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );


	strcpy( pcWriteBuffer, pcHeader );


	/* There is no more data to return after this single string, so return
	pdFALSE. */
	return pdFALSE;
}

3、实现hello的执行函数prvhelloCommand。最后一句return pdFALSE;一定要,这个返回值是告诉cli组件函数执行完毕。

cli会一直循环调用这个函数,直到函数返回值为pdFALSE。这个非常关键。特别是命令行命令的参数有多个的情况下。

/*注册xhello命令*/
void vRegisterSampleCLICommands( void )
{
	/* Register all the command line commands defined immediately above. */
	FreeRTOS_CLIRegisterCommand( &xTaskStats );	
	FreeRTOS_CLIRegisterCommand( &xThreeParameterEcho );
	FreeRTOS_CLIRegisterCommand( &xParameterEcho );
	FreeRTOS_CLIRegisterCommand( &xhello );
	FreeRTOS_CLIRegisterCommand( &xled );
	#if( configGENERATE_RUN_TIME_STATS == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xRunTimeStats );
	}
	#endif
	
	#if( configINCLUDE_QUERY_HEAP_COMMAND == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xQueryHeap );
	}
	#endif

	#if( configINCLUDE_TRACE_RELATED_CLI_COMMANDS == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xStartStopTrace );
	}
	#endif
}

最后一步相对简单,将xhello 结构体注册到cli组件。
编译运行,输入hello,便可打印出hello world。

3.2 实现开关led

要求:终端输入 led on 点亮一个led;输入led off 关闭一个led。

这里 on 和off便是led这个命令的参数。这个跟linux下终端基本上是一致的。这里的参数数量是1.
具体代码如下:

定义xled结构体,这里要注意,参数要设置为1。命令带有一个参数。

static BaseType_t ledCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );
static const CLI_Command_Definition_t xled =
{
	"led", /* The command string to type. */
	"\r\nled:\r\n control led on and off  \r\n",
	ledCommand, /* The function to run. */
	1 /* No parameters are expected. */
};

led命令的实现函数调用了pcParameter = FreeRTOS_CLIGetParameter函数来获取当前的命令行参数。参数结果为pcParameter

static BaseType_t ledCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
BaseType_t xParameterStringLength, xReturn;
static UBaseType_t uxParameterNumber = 1;

	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );


		/* Obtain the parameter string. */
	pcParameter = FreeRTOS_CLIGetParameter
					(
						pcCommandString,		/* The command string itself. */
						uxParameterNumber,		/* Return the next parameter. */
						&xParameterStringLength	/* Store the parameter string length. */
					);

	/* Sanity check something was returned. */
	configASSERT( pcParameter );

	/* Return the parameter string. */
	memset( pcWriteBuffer, 0x00, xWriteBufferLen );
	sprintf( pcWriteBuffer, "%d: ", ( int ) uxParameterNumber );
	strncat( pcWriteBuffer, pcParameter, ( size_t ) xParameterStringLength );
	strncat( pcWriteBuffer, "\r\n", strlen( "\r\n" ) );

	if(strcmp(pcParameter,"on")==0)
		led_on();
	
	if(strcmp(pcParameter,"off")==0)
		led_off();	
	xReturn = pdFALSE;

	return xReturn;
}

获取到了参数后,将参数与 on 与off 对比,再分别调用led的底层控制函数。
最后返回pdFALSE。

void vRegisterSampleCLICommands( void )
{
	/* Register all the command line commands defined immediately above. */
	FreeRTOS_CLIRegisterCommand( &xTaskStats );	
	FreeRTOS_CLIRegisterCommand( &xThreeParameterEcho );
	FreeRTOS_CLIRegisterCommand( &xParameterEcho );
	FreeRTOS_CLIRegisterCommand( &xhello );
	FreeRTOS_CLIRegisterCommand( &xled );
	#if( configGENERATE_RUN_TIME_STATS == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xRunTimeStats );
	}
	#endif
	
	#if( configINCLUDE_QUERY_HEAP_COMMAND == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xQueryHeap );
	}
	#endif

	#if( configINCLUDE_TRACE_RELATED_CLI_COMMANDS == 1 )
	{
		FreeRTOS_CLIRegisterCommand( &xStartStopTrace );
	}
	#endif
}

3.3实现sum函数

要求,终端输入 sum 1 2 3 ,终端输出 1+2+3的结果。sum命令的参数数量不定。
这里有个要求是参数数量不定,3.2中实现的参数是1个。那么参数不定长该怎么设置呢,先看实现代码。

注意这里要设置第xsum结构体的第4个参数为-1,表示命令的参数不定长。

static BaseType_t prvsumCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );
static const CLI_Command_Definition_t xsum =
{
	"sum", /* The command string to type. */
	"\r\nsum:\r\n sum of numbers  \r\n",
	prvsumCommand, /* The function to run. */
	-1 /* No limit of parameters num. */
};

实现函数如下:

static BaseType_t prvsumCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
BaseType_t xParameterStringLength, xReturn;
static UBaseType_t uxParameterNumber = 1;
static uint32_t sum_num = 0;
	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );

		/* Obtain the parameter string. */
	pcParameter = FreeRTOS_CLIGetParameter
					(
						pcCommandString,		/* The command string itself. */
						uxParameterNumber,		/* Return the next parameter. */
						&xParameterStringLength	/* Store the parameter string length. */
					);

	if( pcParameter != NULL )
	{
		pcWriteBuffer[ 0 ] = 0x00;   //每次运行如果不需要输出,就要将pcWriteBuffer清0,至少第一个字节清0
		sum_num += atoi(pcParameter);
		/* There might be more parameters to return after this one. */
		xReturn = pdTRUE;
		uxParameterNumber++;
	}
	else
	{
		if(uxParameterNumber == 1)
		{
			memset( pcWriteBuffer, 0x00, xWriteBufferLen );
			strcpy( pcWriteBuffer, "no parameters!\r\n" );			
		}
		else
		{
			memset( pcWriteBuffer, 0x00, xWriteBufferLen );
			sprintf( pcWriteBuffer, "sum = %d  \r\n", sum_num );	
			sum_num = 0;
		}
		/* No more parameters were found.  Make sure the write buffer does
		not contain a valid string. */
		// pcWriteBuffer[ 0 ] = 0x00;



		/* Start over the next time this command is executed. */
		uxParameterNumber = 1;
					/* No more data to return. */
		xReturn = pdFALSE;
	}

	
	return xReturn;
}

依次读取命令行参数,将参数通过atoi函数转换成为int型。sum_num变量累加输入的参数值。如果读取到得一个参数为空,则终端输出 无参数的提示信息。读取到了最后一个参数后,输出最终结果。

注意,每次运行如果不需要输出,就要将pcWriteBuffer清零,如果里面非空,则每次运行都会输出

注意,每次运行运行完成后要将sum_num清0,因为该值设置的是static类型
到最终输出时返回pdFALSE表示函数要结束执行。注册函数就不贴出来了,看上文。

最终执行效果如图:
其中计算结果7就是因为第一次运行没有清零sum_num。导致累加了第一次的1

在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-04-27 11:29:12  更:2022-04-27 11:29:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 2:30:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码