课程来源: 哔哩哔哩RT-Thread官方。
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闪烁一次。第一节课工程完毕。
对系统的运行流程进行讲解,首先介绍了 “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的主函数线程,进入主函数。
如果要支持 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点 改出来了。成功在终端打印出来。
使用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);
//只要用宏进行修饰了,就会自动调用这个函数。
首先创建两个文件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行 开启宏定义,之后输入相应的代码。
添加文件之后,例程完成。在串口调试助手上打印出信息。注意:在创建线程之后,要在主函数初始化。
发现线程的名称被截断了,实际上线程的名称是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
空闲钩子函数。线程管理.
如何将自己写的函数导入到内核空间去。
新建2个头文件chipinfo.c,chipinfo.h,保存到Dev文件夹下面。要把头文件放入到
"config.h"
中
显示芯片的ID:
#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);
连接终端调试软件,打印出来了芯片的信息。
蜂鸣器操作正常,首先在系统初始化环节关闭初始化操作。
其次,在蜂鸣器初始化操作中添加,那么蜂鸣器不会在主函数中初始化,而是在board中初始化。
最后在蜂鸣器的头文件中添加:
信号量怎么使用呢?
如果要使用信号量的话,首先要在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打印数据。
首先对外部中断进行初始化,修改中断处理函数,配置好按键的中断初始化函数之后。再在中断服务函数中写代码。在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);
}
}
}
第一节课的 中断消息队列创建完成之后,硬件跑通没有问题。一直打印信息是因为还没有进行消抖操作。
第二节:对消息队列进行处理,基于消息和消息队列的处理。
首先在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;
}
本节课介绍如何通过一个结构体,把之前几节课创建的线程整合到一个结构体中。
首先进行定时器的初始化,在系统初始化函数中添加初始化代码。
Tim2Init(72,1000);//中断周期为1ms,用于按键扫描
在button.c和button.h中介绍了按键的初始化和按键状态的代码。
在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打印出如下信息。
在config.h中添加全局变量。
EXT rt_timer_t ADCProcessSoftTimer;//软件定时器控制块指针
修改button.c中的函数,按键按下的处理。三个按键实现定时器的启动,按下key1时,定时器启动。按下key1停止。key2 增加周期时间。运行过程中也可以按下key2,按下key3,周期时间减小。
参照官方文档对定时器进行讲解。
文档链接.
hardtimer模式在中断的上下文运行的,softimer模式在线程的上下文运行的,在线程的上下文运行的话,时间的回调函数有可能受到系统的线程调度的影响。默认情况下是hardtimer模式。如果要启用软件定时器 要在rtconfig.h启动宏。
在移植完成正点原子的DS18B20实验例程之后,对实验例程的延时函数进行了修改。之后初始化操作,完成之后,打印输出信息。
在开发板上移植完成例程之后,实验终端现象如图所示,能够打印出DS18B20的温度信息。
首先打开2个网络调试助手,服务器端和客户端。配置同样的地址和端口号。实现数据收发功能。
调试ESP8266
AT呼叫是否应答,之后复位ESP8266,再设置模式为1,STA+AP模式共存,既可以联网也可以,做路由终端。每次要发送新行。
AT+CIFSR 查看模块的IP地址的指令。
开启透传模式,所谓透传就是待会模块跟助手通讯的时候发的所以东西都是数据。
连接到服务器,模块要断开的话,AT+CIPCLOSE。要让AT指令起作用要退出透传模式,+++是退出透传的命令。
详细介绍如何使用ESP8266了连接oneNet云平台。
使用空闲中断的方式在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文件并且对两个文件进行讲解。
在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命令,实现功能。
创建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.);
配置完成代码之后,在终端对系统调试,调试结果如下所示:
透传成功:
发送信息成功:
传感器检测成功 数据有问题,是因为传感器连接稳定,手持着传感器发送数据。