RT-Thread基础学习(三)-RT-Thread Nano入门笔记_rt-thread nano源码-程序员宅基地

技术标签: STM32  操作系统  RT-Thread  嵌入式  

课程来源: 哔哩哔哩RT-Thread官方

第0课RT-Thread Nano介绍-添加源码

RT-Thread Nano是RT-Thread的裁剪版本。
在这里插入图片描述
在文档中心下载源码之后,打开界面如图所示。板级支持包内容很简介,比RT-Thread少很多文件。components中包括的finish文件,依旧是通过串口输入的命令行的文件。
在这里插入图片描述
libcpu是所支持的文件包括类型。其中cortex-m3又支持不同的编译器,其中包括IAR,KEIL,GCC。
在这里插入图片描述
src文件夹是操作系统的内核源码。其中包括了操作系统的C文件。
在这里插入图片描述

系统的移植

在source下创建新的文件夹(RT-Thread),复制操作系统的文件到该文件夹下,删除了许多多余文件,只留下了需要的文件,将代码设置为只读防止误操作。操作完毕之后的界面如图所示。
在这里插入图片描述
之后添加在工程中添加文件路径和文件。
配置完成第一个文件之后,运行第一个例程代码,是LED灯,代码如下。

int main(void)
{
    	
	while(1)
	{
    
   	LedControl();	
	}
}

在封装好的函数中对LED灯进行操作,每隔1S亮灭一次。

void LedControl(void)
{
    
	GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);
	rt_thread_mdelay(1000);
    GPIO_ResetBits(GPIOA, GPIO_Pin_0|GPIO_Pin_1);
	rt_thread_mdelay(1000);
	
}

用逻辑分析仪对代码进行仿真,界面如图所示,仿真现象完全符合例程要求。代码移植成功。
在这里插入图片描述

总结

下载下来源码,在裸机工程创建文件夹,把bsp、components、include、libcpu、src文件文件拷贝到工程文件,把bsp的board.c和rtconfig.h拷贝到main文件夹下。之后在工程中添加源码,,添加头文件和路径,注释掉裸机开发的代码。重点是系统滴答函数的配置。

void SysTickInit(void)
{
    
	SysTick_Config(SystemCoreClock/RT_TICK_PER_SECOND);
	//配置完成后系统滴答时间为1ms,1S1000个滴答
	//如果改成100,滴答一次是10ms
}

在探索者开发板上的移植操作

在探索者开发板移植操作系统之后的界面如图所示。中间遇到很多问题,比如间接性的调用了头文件,要把头文件用extern void 在文件中定义一下,才能调用不警告。
在这里插入图片描述

在这里插入图片描述初始化操作。
在这里插入图片描述
在这里插入图片描述
烧入到开发板,两个LED等每间隔1S闪烁一次。第一节课工程完毕。

第1课RT-Thread Nano启动运行流程分析

对系统的运行流程进行讲解,首先介绍了 “rtconfig.h”中各个宏定义的作用。

#define RT_THREAD_PRIORITY_MAX  8//系统中断优先级最高是8
#define RT_TICK_PER_SECOND  1000//系统滴答时间为1ms
#define RT_ALIGN_SIZE   4//CPU默认操作为4个字节
#define RT_NAME_MAX    8//内核对象的最大长度
#define RT_USING_COMPONENTS_INIT //组件的初始化
#define RT_USING_USER_MAIN //用户的主函数
#define RT_MAIN_THREAD_STACK_SIZE     256 //主函数或者主线程所对应的堆栈的大小
#define RT_DEBUG_INIT 0 //初始的DEBUG配置为0
#define RT_USING_TIMER_SOFT   0 //表明关闭的软件的定时器,用到了硬件的定时器
#define RT_TIMER_THREAD_PRIO   4  //时间线程的优先级 4
#define RT_TIMER_THREAD_STACK_SIZE  512 //时间线程的堆栈大小 512
#define RT_USING_MUTEX //线程通信互斥
#define RT_USING_EVENT//线程通信事件
#define RT_USING_MAILBOX//线程通信邮箱
#define RT_USING_SMALL_MEM //内存管理
#define RT_CONSOLEBUF_莎·123455SIZE   128//控制台大小128

之后,加入断点,对函数进行仿真运行,观察启动流程。一开始不是在主函数运行的,在main.c之前跳入 components.c submain()之后到rtthread_startup();之后对相关硬件进行板子初始化、显示版本信息、定时器列表初始化、应用函数初始化、调度初始化,空闲线程初始化、创建初始化的线程、空闲钩子函数初始化,最后启动调度器。之后找到了main的主函数线程,进入主函数。

第2课RT-Thread Nano-rt_kprintf的支持

如果要支持 k_printf 函数,就要在usart.c中添加如下代码

void rt_hw_console_output(const char *str)  //实现该函数,才能使用rt_kprintf
{
    
	 /* 进入临界段 */  
	 //禁止操作系统的调度,进入临界段的代码不允许打断,当rt_scheduler_lock_nest>=1时,调度器停止调度。 
  rt_enter_critical();
	while(*str!='\0')
	{
    
		 /* 换行 */
    if (*str == '\n')//RT-Thread 系统中已有的打印均以 \n 结尾,而并非 \r\n,所以在字符输出时,需要在输出 \n 之前输出 \r,完成回车与换行,否则系统打印出来的信息将只有换行
    {
    
       USART_SendData(USART1, '\r');
	     while(USART_GetFlagStatus(USART1, USART_FLAG_TC)== RESET);
    }
		USART_SendData(USART1, *(str++));
	  while(USART_GetFlagStatus(USART1, USART_FLAG_TC)== RESET);	
	}
	 /* 退出临界段 */  
  rt_exit_critical();  //注意:使用进入临界段语句rt_enter_critical(); 一定要使用退出临界段语句 rt_exit_critical();否则调度器锁住,无法进行调度
}

在探索者开发板上的移植操作

由于中午串口没有初始化操作,导致一个问题,一直卡在死循环里面出不去,从中午12点改到晚上9点 改出来了。成功在终端打印出来。

在这里插入图片描述

第3课RT-Thread Nano-Finsh的支持

使用Finsh组件三步骤:
1.实现该函数及rt_hw_console_output函数;
2.rtconfig.h中开启RT_USING_FINSH宏;
3.添加Finsh组件(cmd.c、msh.c、shell.c)。
首先打开 finish组件 在rtconfig.h添加#define RT_USING_FINSH,在串口中串口输入的函数,主要实现输入命令的功能,在usart.c中添加如下函数。

char rt_hw_console_getchar(void)
{
      //查询方式实现,记得将Usart1初始化中的中断接收配置相关代码注释掉
	   int ch = -1;
	   /*等待串口1输入数据*/
     if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
		 {
    
         ch = (int)USART_ReceiveData(USART1);
		 USART_ClearFlag(USART1, USART_FLAG_RXNE);
		 }
		 else
		 {
    
			 
         if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
         {
    
             USART_ClearFlag(USART1, USART_FLAG_ORE);
         }
         rt_thread_mdelay(10);
			 
		 }

     return ch;
}

配置完成之后,进入仿真界面,但是按下tab键没有反应,要让查询实现功能,要关闭掉中断函数,如下图所示。
在这里插入图片描述输入命令 list_thread可以查看到当前运行的情况。
如何导入自己想要添加的命令呢?
使用了这个宏定义

MSH_CMD_EXPORT(version, show RT-Thread version information);
//前面是函数名,后面是函数
INIT_APP_EXPORT(finsh_system_init);
//只要用宏进行修饰了,就会自动调用这个函数。

在探索者开发板上的移植操作

在串口函数中添加相应的代码,成功实现终端输入界面。
在这里插入图片描述

第4课RT-Thread Nano-线程创建

首先创建两个文件Task.c和Task.h。在Task.h中添加如下代码。

#ifndef __TASK_H__
#define __TASK_H__
void TaskInit(void);
#endif

以后要用到的三个常用的头文件rtthread.h rthw.h rtdef.h

线程的静态创建与删除 在rtthread.h 116行

rt_err_t rt_thread_init(struct rt_thread *thread,
                        const char       *name,
                        void (*entry)(void *parameter),
                        void             *parameter,
                        void             *stack_start,
                        rt_uint32_t       stack_size,
                        rt_uint8_t        priority,
                        rt_uint32_t       tick);
rt_err_t rt_thread_detach(rt_thread_t thread);

线程的动态创建与删除

rt_thread_t rt_thread_create(const char *name,
                             void (*entry)(void *parameter),
                             void       *parameter,
                             rt_uint32_t stack_size,
                             rt_uint8_t  priority,
                             rt_uint32_t tick);
rt_err_t rt_thread_delete(rt_thread_t thread);

这里主要介绍动态线程的创建,在task.c文件中添加如下代码。

#include "config.h"
#include "Task.h"
static rt_thread_t led_thread;
void led_thread_entry(void *parameter);
void TaskInit(void)
{
    
//返回值还是需要的,否则无法通过rt_thread_startup启动
	led_thread = rt_thread_create("ledThread",       /* 线程名字 */
                                led_thread_entry,  /* 线程入口函数 */
                                RT_NULL,           /* 线程入口函数参数 */
                                256,               /* 线程栈大小 */
                                2,                 /* 线程的优先级 */
                                10                 /* 线程时间片 */
                   	            );
	if(led_thread != RT_NULL)//分配成功,加入到就绪队列中。
	{
    
		rt_thread_startup(led_thread);
	}
}
void led_thread_entry(void *parameter)
{
    
	while(1)
	{
    
		LedToggle(GPIOA,GPIO_Pin_1);
	  rt_thread_mdelay(2000);	
rt_kprintf("System Running Time:%d s \n",rt_tick_get()/RT_TICK_PER_SECOND);
//每2S打印一次系统时间。比如得到当前滴答是2000,1s是1000个滴答,那么就是2S。
	}
}

仿真运行实验观察试验现象,如下图所示。线程1每1S翻转一次,线程2每2S翻转一次。
在这里插入图片描述

在探索者开发板上的移植操作

首先在rtconfig.h的107行 开启宏定义,之后输入相应的代码。
在这里插入图片描述添加文件之后,例程完成。在串口调试助手上打印出信息。注意:在创建线程之后,要在主函数初始化。

在这里插入图片描述

第5课RT-Thread Nano-线程讲解

发现线程的名称被截断了,实际上线程的名称是9个字符因为在rtconfig.h中被限制了只要改变宏定义即可,
#define RT_NAME_MAX 16 创建动态线程的时候,要使能堆栈中的#define RT_USING_HEAP,当堆栈创建的非常大的时候,会溢出,在board.c的第51行#define RT_HEAP_SIZE 2048改变堆栈大小。之后在文档中介绍了线程其他操作,线程调度的钩子函数,要用到钩子函数的时候要把rtconfig.h#define RT_USING_HOOK打开,使用空闲钩子函数的话,也要打开#define RT_USING_IDLE_HOOK空闲钩子函数。线程管理.

在探索者开发板上的移植操作

在这里插入图片描述

第6-7课RT-Thread Nano-自动初始化及MSH-EXPORT

如何将自己写的函数导入到内核空间去。
新建2个头文件chipinfo.c,chipinfo.h,保存到Dev文件夹下面。要把头文件放入到
"config.h"在这里插入图片描述

显示芯片的ID:

定义1个32位的数组。里面存放3个数据,最高32个字节。
在这里插入图片描述在这里插入图片描述

显示存储器的信息:
在这里插入图片描述

#include "config.h"
#include "ChipInfo.h"


uint32_t ChipUniqueID[3];
void GetChipID(void)//获取CPU的ID函数,每个芯片都有唯一的 96_bit unique ID
{
    
	ChipUniqueID[0]=*(volatile uint32_t *)(0x1FFFF7F0);//ID号高32位  对地址转换成指针后再取指针取到内容。
	ChipUniqueID[1]=*(volatile uint32_t *)(0x1FFFF7EC);//强制转换成指针类型,添加一边修饰符,表示最后一次数值
	ChipUniqueID[2]=*(volatile uint32_t *)(0x1FFFF7E8);//ID号低字节
	rt_kprintf("\nChip ID is:0x%08X-%08X-%08X\n\n",ChipUniqueID[0],ChipUniqueID[1],ChipUniqueID[2]);
	
}
MSH_CMD_EXPORT(GetChipID, Get 96_bit unique Chip ID);
//在cmd.c中找到59行中找到`MSH_CMD_EXPORT(version, show RT-Thread version information);`参数1位为函数名,参数2位说明信息。

void GetFlashCapacity(void)
{
    
	rt_kprintf("\nChip Flash capacity is:%dK \n\n",*(volatile uint16_t *)(0x1FFFF7E0));
	//存储器信息转换成16位的信息,对应的图2
}
MSH_CMD_EXPORT(GetFlashCapacity, Get Chip Flash Capacity);

第二节

GPIO初始化的方法:在rtdef.h的205行找到初始化的宏定义。

void BeepGpioInit(void)
{
    
	GPIO_InitTypeDef  GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_8 ;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);	
}
INIT_BOARD_EXPORT(BeepGpioInit);// 在rtdef.h中205行
//INIT_DEVICE_EXPORT(BeepGpioInit);
//INIT_ENV_EXPORT(BeepGpioInit);

工作流程的先后顺序。
在这里插入图片描述自动初始化参考 CSDN例程

在探索者开发板上的移植操作

找到开发板的中文参考手册。
在这里插入图片描述更改F4芯片信息:
在这里插入图片描述在这里插入图片描述

连接终端调试软件,打印出来了芯片的信息。

在这里插入图片描述
蜂鸣器操作正常,首先在系统初始化环节关闭初始化操作。在这里插入图片描述
其次,在蜂鸣器初始化操作中添加,那么蜂鸣器不会在主函数中初始化,而是在board中初始化。
在这里插入图片描述最后在蜂鸣器的头文件中添加:
在这里插入图片描述

蜂鸣器操作正常。
在这里插入图片描述

第8课RT-Thread Nano-串口接收(信号量的使用)

信号量怎么使用呢?
如果要使用信号量的话,首先要在rttconfig.h中打开#define RT_USING_SEMAPHORE宏。之后在rtthread.h的第292行中找到初始化定义。一般变量结尾为_t都为指针型变量。

rt_err_t rt_sem_init(rt_sem_t    sem,
                     const char *name,
                     rt_uint32_t value,
                     rt_uint8_t  flag);
rt_err_t rt_sem_detach(rt_sem_t sem);
//静态创建和脱离
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);
rt_err_t rt_sem_delete(rt_sem_t sem);
//动态创建和删除

首先,在config.h中加入全局变量的定义

EXT rt_sem_t usart2_recv_sem;//定义串口2接收信号量控制块指针

在task.c中创建信号量的任务。在rtdef.h中找到信号量的模式配置。
创建中添加如下代码:

	usart2_recv_sem = rt_sem_create("usart2_recv_sem",  //信号量名字
									0,                  //信号量初始值
								   RT_IPC_FLAG_FIFO    //信号量模式 FIFO(0x00)
	                                );
	if(usart2_recv_sem != RT_NULL)
									rt_kprintf("信号量usart2_recv_sem创建成功\n\n");

如何利用信号量呢?在usart.c中95行添加空闲中断

USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//添加串口空闲中断使能,请不要使用USART_IT_RXNE|USART_IT_IDLE,记住分开写两条语句
//空闲中断无法软件仿真,必须硬件仿真

之后在中断回调函数中添加代码,将串口2接收的数据存入到接收的中断缓冲区。
例程代码在usart.c的第9行。

 uint8 RecCh;
RecCh = (uint8)USART_ReceiveData(USART2);
g_USART2_RxBuf[g_USART2_RecPos++] = RecCh;


if( USART_GetFlagStatus(USART2,USART_FLAG_IDLE)==SET ) 	// 串口溢出错误
	{
    
		#if USART2_EN == 1 
					 //用户代码
		g_USART2_RxBuf[g_USART2_RecPos] = '\0';
		rt_sem_release(usart2_recv_sem);//释放一个信号量,表示数据已接收;给出二值信号量 ,发送接收到新数据帧标志,供前台线程查询
		#endif
		USART_ReceiveData(USART2);
		//使用该语句清除空闲中断标志位,请不要使用USART_ClearITPendingBit(USART2, USART_IT_IDLE);该语句无法达到效果
	}

在task.c中创建串口2的线程。

static rt_thread_t usart2_recv_thread = RT_NULL;
void usart2_recv_thread_entry(void *parameter);
usart2_recv_thread = rt_thread_create("usart2_recv_thread",  /* 线程名字 */
                                usart2_recv_thread_entry,  /* 线程入口函数 */
                                RT_NULL,           /* 线程入口函数参数 */
                                512,               /* 线程栈大小 */
                                2,                 /* 线程的优先级 */
                                10                 /* 线程时间片 */
                   	            );
	if(usart2_recv_thread != RT_NULL)
	{
    
		rt_thread_startup(usart2_recv_thread);//线程的启动
	}

最后,在task.c末尾书写线程入口函数。
在rtthread.h中找到rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);

void usart2_recv_thread_entry(void *parameter)
{
    
	rt_err_t uwRet = RT_EOK;//如果得到信号量,返回RT_EOK
	while(1)//线程是无限循环的函数
	{
    
		 uwRet =rt_sem_take(usart2_recv_sem, RT_WAITING_FOREVER);//获取串口2接收帧完成信号量,等待时间为redef.h第586行,一直等待,阻塞。
		 if(RT_EOK == uwRet )
		 {
    
			 #if USART2_EN == 1
				 rt_kprintf("Usart2 Receive Data:%s\n",g_USART2_RxBuf);
				 
				 if(strstr((char*)g_USART2_RxBuf,"BeepOn")!=NULL)
				 {
    
					 BeepOn();
				 }
				 if(strstr((char*)g_USART2_RxBuf,"BeepOff")!=NULL)
				 {
    
					 BeepOff();
					 
				 }
				memset(g_USART2_RxBuf,0,USART2_RX_BUF_SIZE);//清零,清空缓冲区
				 g_USART2_RecPos = 0;
			 #endif
		 }
	}
}

大致思路:1、conifg.h 定义信号量的指针
2、串口修改串口2的配置,使能串口2的空闲配置。
3、一旦释放的信号量,用线程获取信号量,空闲中断发生,收到一帧数据,发送一个信号量过来,得到一个数据之后,对数据进行处理,往串口1打印数据。

第9-10课RT-Thread Nano-外部中断(消息队列的使用)

首先对外部中断进行初始化,修改中断处理函数,配置好按键的中断初始化函数之后。再在中断服务函数中写代码。在config.h中定义消息队列的指针。

EXT rt_mq_t key_mq;//定义按键消息队列控制块

在TaskInit中创建消息队列以及线程。

key_mq = rt_mq_create("key_mq", //消息队列名字
						32,  	//消息的最大长度, bytes
						10,   	//消息队列的最大容量(个数)
			  RT_IPC_FLAG_FIFO  //队列模式 FIFO
	                     );
		if(key_mq != RT_NULL)
		rt_kprintf("消息队列key_mq创建成功\n\n");


key_thread = rt_thread_create("key_thread",       /* 线程名字 */
                                key_thread_entry,  /* 线程入口函数 */
                                RT_NULL,           /* 线程入口函数参数 */
                                512,               /* 线程栈大小 */
                                2,                 /* 线程的优先级 */
                                10                 /* 线程时间片 */
                   	            );
	if(key_thread != RT_NULL)
	{
    
		rt_thread_startup(key_thread);
	}

在Task.c创建线程。

static rt_thread_t key_thread = RT_NULL;
void key_thread_entry(void *parameter);
void key_thread_entry(void *parameter)
{
    
	rt_err_t uwRet = RT_EOK;
	uint8_t r_queue[32];//用于接收key_mq消息队列信息,根据实际需要定义大小,或者将大小定义为一个宏(以方便调整)
	while(1)
	{
     //获取队列信息
		 uwRet = rt_mq_recv(key_mq,
                        r_queue,
                        sizeof(r_queue),
                        RT_WAITING_FOREVER);
		 if(RT_EOK == uwRet )
		 {
    
			 rt_kprintf("%s",r_queue);//打印消息内容
		 }
		 else
		 {
    
			 rt_kprintf("数据接收错误,错误代码:0x%lx\n\n",uwRet);
		 }
	}				
}

在外部中断2函数中写发送消息的代码,

void EXTI2_IRQHandler (void)
{
    
	if(EXTI_GetITStatus(EXTI_Line2) == SET )
	{
    
		//用户代码
rt_mq_send(key_mq,		    		// 写入(发送)队列的ID(句柄) 
"Key2(PE.2) EXIT Occur \n", 		// 写入(发送)的数据
sizeof("Key2(PE.2) EXIT Occur \n")	// 数据的长度 
							);
		//--------------------------------
		EXTI_ClearFlag(EXTI_Line2);
	}
}

在外部中断3函数中写发送消息的代码,

void EXTI3_IRQHandler (void)
{
    
	if(EXTI_GetITStatus(EXTI_Line3) == SET )
	{
    
		//用户代码
	rt_mq_send(key_mq, 					// 写入(发送)队列的ID(句柄) 
    "Key1(PE.3) is Pressed\n",		    // 写入(发送)的数据 
     sizeof("Key1(PE.3) is Pressed\n")	// 数据的长度 
						);
		//--------------------------------
		EXTI_ClearFlag(EXTI_Line3);
	}
}

打开仿真调试,没有问题。硬件有问题 后期要进行消抖操作。
在这里插入图片描述

第二节课

在config.h中定义消息枚举类型。

typedef enum //定义消息枚举类型
{
    
	MSG_NULL = 0,
	/******************************/
	//添加用户消息常量,例如:MSG_XXX,
	MSG_KEY1_PRESS,
	MSG_KEY2_PRESS,
	/******************************/
	MSG_NUM//消息队列的数目。
}MSG_TYPE;

在外部中断2中,添加如下代码。

void EXTI2_IRQHandler (void)
{
    
	MSG_TYPE msg = MSG_KEY2_PRESS;
	if(EXTI_GetITStatus(EXTI_Line2) == SET )
	{
    
		//用户代码
		rt_mq_send(msg_mq,       // 写入(发送)队列的ID(句柄)
		           &msg,         // 写入(发送)的数据所对应地址 
		           sizeof(msg)   // 数据的长度 
							);
		//--------------------------------
		EXTI_ClearFlag(EXTI_Line2);
	}
}

在外部中断3中,添加如下代码。

void EXTI3_IRQHandler (void)
{
    
	MSG_TYPE msg = MSG_KEY1_PRESS;
	if(EXTI_GetITStatus(EXTI_Line3) == SET )
	{
    
		//用户代码
	rt_mq_send(msg_mq,		// 写入(发送)队列的ID(句柄)
						 &msg,  		// 写入(发送)的数据所对应地址 
						 sizeof(msg)// 数据的长度 
						);
		//--------------------------------
		EXTI_ClearFlag(EXTI_Line3);
	}
}

在task.c中,修改如下代码。

static rt_thread_t msg_process_thread = RT_NULL;//消息处理线程控制块指针
void msg_process_thread_entry(void *parameter);//用户消息处理入口函数
msg_process_thread = rt_thread_create("msg_process_thread",   // 线程名字 
       msg_process_thread_entry,  	// 线程入口函数 
						RT_NULL,    // 线程入口函数参数
				        512,        // 线程栈大小 
					    2,          // 线程的优先级 
					    10          // 线程时间片 
							);
  if(msg_process_thread != RT_NULL)
	{
    
		rt_thread_startup(msg_process_thread);
	}

最后添加消息队列处理函数。

void msg_process_thread_entry(void *parameter)
{
    
	rt_err_t uwRet = RT_EOK;
	uint8_t r_queue;//用于接收msg_mq消息队列信息
	
	while(1)
	{
      //获取队列信息
		 uwRet = rt_mq_recv(msg_mq,
							&r_queue,
							sizeof(r_queue),
							RT_WAITING_FOREVER
							);
		 if(RT_EOK == uwRet )
		 {
    
			 switch(r_queue)//根据接收到的消息内容分别进行处理
			 {
    
				 case MSG_KEY1_PRESS:rt_kprintf("Receive message:KEY1(PE.3) is press\n\n");break;
				 case MSG_KEY2_PRESS:rt_kprintf("Receive message:KEY2(PE.2) is press\n\n");break;
				 default: rt_kprintf("No Message!\n\n");break; 
			 }
		 }
		 else
		 {
    
			 rt_kprintf("数据接收错误,错误代码:0x%lx\n\n",uwRet);
		 }
	}				

}

在探索者开发板上的移植操作

第一节课的 中断消息队列创建完成之后,硬件跑通没有问题。一直打印信息是因为还没有进行消抖操作。
在这里插入图片描述
第二节:对消息队列进行处理,基于消息和消息队列的处理。
在这里插入图片描述

第11课RT-Thread Nano-动态创建线程以及代码修改

首先在task.h中定义一个结构体。

typedef struct 
{
    
	//动态创建线程时使用的线程参数结构体
	char *name;
  void (*entry)(void *parameter);
  void       *parameter;
  rt_uint32_t stack_size;
	rt_uint8_t  priority;
	rt_uint32_t tick;
}TaskStruct;

在task.c中定义结构体的数组。

static rt_thread_t dynamic_thread = RT_NULL;//动态线程控制块指针

TaskStruct TaskThreads[] = {
    
			{
    "ledThread", led_thread_entry,  RT_NULL,  256,  5, 10},
			{
    "usart2_recv_thread", usart2_recv_thread_entry, RT_NULL, 512, 2, 	10 	},
			{
    "msg_process_thread",  msg_process_thread_entry, RT_NULL, 512, 2,   10 },
			/*********************************************************/
			//用户添加线程参数
			//例如:{线程名字,线程入口函数,线程入口函数参数,线程栈大小,线程的优先级,线程时间片},
			{
    "",RT_NULL, RT_NULL,RT_NULL,RT_NULL,RT_NULL}
};

在初始化线程中添加

uint8_t TaskThreadIndex = 0;
 while(1)
	 {
    
		 if(strcmp(TaskThreads[TaskThreadIndex].name,"") != 0)
		 {
    
			 
				dynamic_thread = rt_thread_create(TaskThreads[TaskThreadIndex].name,       // 线程名字 
                                TaskThreads[TaskThreadIndex].entry,  // 线程入口函数 
                                TaskThreads[TaskThreadIndex].parameter,           // 线程入口函数参数
                                TaskThreads[TaskThreadIndex].stack_size,               // 线程栈大小 
                                TaskThreads[TaskThreadIndex].priority,                 // 线程的优先级 
                                TaskThreads[TaskThreadIndex].tick                 // 线程时间片
                   	            );
				if(dynamic_thread != RT_NULL)
				{
    
					rt_thread_startup(dynamic_thread);
				}
			  TaskThreadIndex ++;
		 }
		 else
			 break; 
	 }

本节课介绍如何通过一个结构体,把之前几节课创建的线程整合到一个结构体中。

在探索者开发板上的移植操作

修改代码,通过循环创建动态线程。
在这里插入图片描述

第12课RT-Thread Nano-通用定时器(按键消抖)-消息队列

首先进行定时器的初始化,在系统初始化函数中添加初始化代码。

Tim2Init(72,1000);//中断周期为1ms,用于按键扫描

在button.c和button.h中介绍了按键的初始化和按键状态的代码。

在探索者开发板上的移植操作

在这里插入图片描述
在探索者开发板移植完成之后,系统现象如图所示。

第13课RT-Thread Nano-ADC(时钟管理之软件定时器)-1软件定时器的创建

在rtconfig.h函数中开启软件定时器宏定义,使用软件定时器。创建一个线程。否则用的就是hardtimer模式。如何使用?
在ADC.c文件中配置代码:

static rt_timer_t ADCProcessSoftTimer = RT_NULL;//软件定时器控制块指针
static void ADCProcessSoftTimer_callback(void* parameter)
{
    
  printf("\r\n ADC1 CH10(PC0) value = %.2f V \r\n",(float)ADCConvertedValue[0]/4096 * 3.3);
}
int ADCProcessInit()
{
    
	AdcInit();//ADC初始化
	ADCProcessSoftTimer = rt_timer_create("ADCProcessSoftTimer", 
	/* 软件定时器的名称 */
ADCProcessSoftTimer_callback,/* 软件定时器的回调函数 */
 0,			/* 定时器超时函数的入口参数 */
 2*RT_TICK_PER_SECOND,   /* 软件定时器的超时时间(周期回调时间) */
                        RT_TIMER_FLAG_PERIODIC );
                        /* 软件定时器模式 周期模式 */
  /* 启动定时器 */
  if (ADCProcessSoftTimer != RT_NULL) 
      rt_timer_start(ADCProcessSoftTimer);
	return 0;
}
INIT_APP_EXPORT(ADCProcessInit);

由于软件模拟仿真,采集到通道的电压为0。

在探索者开发板上的移植操作

在开发板移植代码完成之后,在每隔2s。串口1打印出如下信息。
在这里插入图片描述

第14课RT-Thread Nano-ADC(时钟管理之软件定时器)-2软件定时器的控制

在config.h中添加全局变量。

EXT rt_timer_t ADCProcessSoftTimer;//软件定时器控制块指针

修改button.c中的函数,按键按下的处理。三个按键实现定时器的启动,按下key1时,定时器启动。按下key1停止。key2 增加周期时间。运行过程中也可以按下key2,按下key3,周期时间减小。

第15课RT-Thread Nano-ADC(时钟管理之软件定时器)-3软件定时器内核代码分析

参照官方文档对定时器进行讲解。
文档链接.
hardtimer模式在中断的上下文运行的,softimer模式在线程的上下文运行的,在线程的上下文运行的话,时间的回调函数有可能受到系统的线程调度的影响。默认情况下是hardtimer模式。如果要启用软件定时器 要在rtconfig.h启动宏。

第16课RT-Thread Nano-DS18B20-高精度微妙延时

在移植完成正点原子的DS18B20实验例程之后,对实验例程的延时函数进行了修改。之后初始化操作,完成之后,打印输出信息。

在这里插入图片描述

在探索者开发板上的移植操作

在开发板上移植完成例程之后,实验终端现象如图所示,能够打印出DS18B20的温度信息。
在这里插入图片描述

第17课RT-Thread Nano-ESP8266-硬件模块测试

首先打开2个网络调试助手,服务器端和客户端。配置同样的地址和端口号。实现数据收发功能。
在这里插入图片描述
调试ESP8266
在这里插入图片描述AT呼叫是否应答,之后复位ESP8266,再设置模式为1,STA+AP模式共存,既可以联网也可以,做路由终端。每次要发送新行。
AT+CIFSR 查看模块的IP地址的指令。
开启透传模式,所谓透传就是待会模块跟助手通讯的时候发的所以东西都是数据。
在这里插入图片描述
连接到服务器,模块要断开的话,AT+CIPCLOSE。要让AT指令起作用要退出透传模式,+++是退出透传的命令。

ESP8266连接oneNet云平台。

详细介绍如何使用ESP8266了连接oneNet云平台。

在个人PC端上的移植操作

通过ESP8266连接成功。
在这里插入图片描述

注册了OneNet账号之后,按照流程创建成功了数据流,
在这里插入图片描述

之后对数据进行可视化操作,发送可变的数据。
在这里插入图片描述

第18课RT-Thread Nano-ESP8266 WIFI-代码添加

使用空闲中断的方式在usart.c中串口3函数中添加如下代码,功能是使能了串口3的空闲中断。

USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);

添加printf输出函数。添加如下代码:

/*
 * 函数名:USART_printf
 * 描述  :格式化输出,类似于C库中的printf,但这里没有用到C库
 * 输入  :-USARTx 串口通道,
 *		     -Data   要发送到串口的内容的指针
 *			   -...    其他参数
 * 输出  :无
 * 返回  :无 
 * 调用  :外部调用
 *         典型应用USART3_printf( USART3, "\r\n this is a demo \r\n" );
 *            		 USART3_printf( USART3, "\r\n %d \r\n", i );
 *            		 USART3_printf( USART3, "\r\n %s \r\n", j );
 */
void USART_printf ( USART_TypeDef * USARTx, char * Data, ... )
{
    
	const char *s;
	int d;   
	char buf[16];
	va_list ap;
	va_start(ap, Data);

	while ( * Data != 0 )     // 判断是否到达字符串结束符
	{
    				                          
		if ( * Data == 0x5c )  //'\'
		{
    									  
			switch ( *++Data )
			{
    
				case 'r':							          //回车符
				USART_SendData(USARTx, 0x0d);
				Data ++;
				break;
				case 'n':							          //换行符
				USART_SendData(USARTx, 0x0a);	
				Data ++;
				break;
				default:
				Data ++;
				break;
			}			 
		}
		else if ( * Data == '%')
		{
    									  //
			switch ( *++Data )
			{
    				
				case 's':										  //字符串
				s = va_arg(ap, const char *);
				
				for ( ; *s; s++) 
				{
    
					USART_SendData(USARTx,*s);
					while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				}
				Data++;
				break;
				case 'd':			
					//十进制
				d = va_arg(ap, int);
				itoa(d, buf, 10);
				for (s = buf; *s; s++) 
				{
    
					USART_SendData(USARTx,*s);
					while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				}
				Data++;
				break;
				default:
				Data++;
				break;
			}		 
		}
		else USART_SendData(USARTx, *Data++);
		while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );
	}
}

在config.h中 常用头文件中添加如下

#include "stdarg.h"//可变参数头文件
#include "stdbool.h"//布尔类型头文件

在usart.h中添加函数的声明:

void UsartSendByte(USART_TypeDef* USARTx,uint8 ch);//发送单个字节的函数
void USART_printf ( USART_TypeDef * USARTx, char * Data, ... )

添加ESP8266的代码。
可以不修改延时函数,只需要在文件中宏定义即可。定义函数如下:

#define RTOSTimeDlyNms(Nms) rt_thread_mdelay(Nms)

添加野火代码的esp8266.c和esp8266.h文件并且对两个文件进行讲解。

第19课RT-Thread Nano-ESP8266 WIFI-代码测试

在source中创建新的文件夹App作为应用代码。新建文件WifiCmdTest.c和WifiCmdTest.h,工程分组添加App分组,添加源文件。在WifiCmdTest.c中添加代码

#include "config.h"
#include "WifiCmdTest.h"
void ATcmd(int argc,char **argv)
{
    
	if(!rt_strcmp(argv[1],"AT"))
	{
    
		ESP8266_Cmd ( "AT", "OK", NULL, 500 );
		printf("%s\r\n",strEsp8266_Fram_Record .Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"RST"))//AT命令
	{
    
		ESP8266_Rst();
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"STA"))//设置为工作站模式
	{
    
		ESP8266_Net_Mode_Choose ( STA );
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else 	if(!rt_strcmp(argv[1],"AP"))//设置为热点模式
	{
    
		ESP8266_Net_Mode_Choose ( AP );
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else 	if(!rt_strcmp(argv[1],"STA_AP"))  //设置为工作站+热点模式
	{
    
		ESP8266_Net_Mode_Choose ( STA_AP );
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
  else 	if(!rt_strcmp(argv[1],"JoinAP")) //加入热点
	{
    
		ESP8266_JoinAP ( argv[2], argv[3]);
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"ipconfig")) //查询本机IP
	{
    
		ESP8266_InquireIP( );
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}	 
	else if(!rt_strcmp(argv[1],"LinkServer")) //加入服务器
	{
    
		if(!rt_strcmp(argv[2],"TCP"))
		 ESP8266_Link_Server ( enumTCP, argv[3], argv[4], Single_ID_0);
		else
		 ESP8266_Link_Server ( enumUDP, argv[3], argv[4], Single_ID_0);
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"CloseLink")) //关闭TCP或UDP连接
	{
    
		ESP8266_Close_Link();
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"Unvarnish")) //开启透传
	{
    
		ESP8266_UnvarnishSend();
		printf("%s\n",strEsp8266_Fram_Record.Data_RX_BUF);
	}
	else if(!rt_strcmp(argv[1],"SendData")) //透传时发送数据,数据间不能包含空格;若需发送空格数据请加双引号
	{
    
		ESP8266_SendString ( ENABLE, argv[2], rt_strlen(argv[2]), Single_ID_0 );
		
		printf("Send Data:%s\r\n",argv[2]);
	}
	else if(!rt_strcmp(argv[1],"ExitUnvarnish")) //关闭透传
	{
    
		ESP8266_ExitUnvarnishSend ();
		printf("ExitUnvarnish Success!\r\n");
	}
}
MSH_CMD_EXPORT(ATcmd, ESP8266 Test.);



在探索者开发板上的移植操作

ESP8266模块只用到了4个引脚分别是串口PB10,PB11和VCC,GND所以就把另外两个初始化的引脚给注释掉了。在完成代码移植之后,打开终端调试助手,测试AT命令,实现功能。
在这里插入图片描述
在这里插入图片描述

第20课RT-Thread Nano-ESP8266 WIFI-温度上传至本地服务器。

创建2个温度上传文件,添加源文件。在上传的文件中添加如下代码:

#include "config.h"
#include "TemperatureUploadLocalSer.h"
static rt_timer_t Ds18B20ProcessSoftTimer;
static char SendData[30];//发往服务器的包,暂存数组
static void Ds18B20ProcessSoftTimer_callback(void* parameter)
{
    
	sprintf(SendData,"\r\nTemperature: %.1f\r\n",DS18B20_GetTemp_SkipRom());
  ESP8266_SendString ( ENABLE, SendData, strlen(SendData), Single_ID_0 );
//   printf ( "\r\nTemperature: %.1f\r\n", DS18B20_GetTemp_SkipRom() );
}
static void SensorDataUploadCycle(void* parameter)
{
    
	rt_timer_start(Ds18B20ProcessSoftTimer);
}
MSH_CMD_EXPORT(SensorDataUploadCycle,SensorDataUploadCycle.);

static void SensorDataUploadStop(void* parameter)
{
    
	rt_timer_stop(Ds18B20ProcessSoftTimer);
}
MSH_CMD_EXPORT(SensorDataUploadStop,SensorDataUploadStop.);
int SensorDataSendToServerInit()
{
    
	Ds18B20ProcessSoftTimer = rt_timer_create("Ds18B20ProcessSoftTimer", /* 软件定时器的名称 */
                        Ds18B20ProcessSoftTimer_callback,/* 软件定时器的回调函数 */
                        0,			/* 定时器超时函数的入口参数 */
                        5*RT_TICK_PER_SECOND,   /* 软件定时器的超时时间(周期回调时间) */
                        RT_TIMER_FLAG_PERIODIC );
                        /* 软件定时器HARD_TIMER模式 周期模式 */
	return 0;
}
MSH_CMD_EXPORT(SensorDataSendToServerInit, Temperature Data Sendto Server Init.);

在探索者开发板上的移植操作

配置完成代码之后,在终端对系统调试,调试结果如下所示:
透传成功:
在这里插入图片描述

发送信息成功:
在这里插入图片描述
传感器检测成功 数据有问题,是因为传感器连接稳定,手持着传感器发送数据。
在这里插入图片描述

第21课RT-Thread Nano-NTP-获取网络时间

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46152793/article/details/118027879

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签