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

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文