谭卓琳
谭卓琳
This guy hasn't written anything yet

注册于 2 years ago

回答
4
文章
1
关注者
0

我前几天也试过,但是无奈Nano移植这个LWIP的资料不多,而且LWIP我也不是很熟,所以直接换Master了

作品背景

需求1、有时候人住的楼层比较高,早上上班的时候经常会忘记将垃圾提下楼,一般下班回来已经很累了,根本不会想到再跑一趟去扔垃圾,但是垃圾不扔,就会放多一夜,各种惹蚊子。因此就想到了实现一个语音提示的生活助手。当我出门上班的时候,生活助手就会提醒我把垃圾提上。
需求2、经常会忘记看天气预报,然后忘记带雨伞出门,结果到了半路发现会下雨,着实很尴尬。因此这个生活助手会在我出门上班前,获取网络上的天气预报,如果今天会下雨的话,就提醒我带雨伞。
需求3、家里面的热水器等家用电器,不用的时候不希望它开着浪费电,但是回到家希望能洗到热水澡,因此生活助手要能够在设定的时间打开热水器,或者其他家用电器。最好可以通过手机直接控制家里面的一些电器。

作品材料

1、硬件平台:主板使用的是野火的STM32F103霸道开发板。因为需要获取网络时间和天气预报,因此需要外搭一个ESP8266串口WIFI模块进行联网。此外还需要一个串口蓝牙模块,用来通过手机对主板进行一些控制。
2、软件平台、RT-Thread Nano

功能介绍

1、设备在上电以后,会主动获取NTP服务器上的时间,并将时间更新到STM32的RTC实时时钟上。
2、当用户设置了起床闹钟的时候,会播放【老大,早上好!】提醒用户起床。
3、当用户设置了降雨提醒和扔垃圾提醒的时候,会获取心之天气上的天气预报信息,播放【老大,今天会下雨哦,记得带雨伞】和【出门前记得把垃圾带上,老大】提醒用户。
3、当用户设置了运动提醒时,会播放【工作一天了,不要老是对着电脑,运动一下吧】提醒用户运动。
4、当用户设置了睡眠提醒时,会播放【该休息了,老大】提醒用户睡觉。
5、当用户设置了定时打开热水器时,会在指定的时间将热水器打开。
6、当用户使用手机连接蓝牙到设备以后,可以控制家里的电灯亮灭、亮度、颜色等等、也可以控制热水器开关、或者播放音乐。

以下是获取和设置闹钟的命令
在这里插入图片描述
在这里插入图片描述
以下是蓝牙控制开关,通过这些开关可以控制设备的灯光亮灭、亮度、延时、热水器开关(这里通过蜂鸣器来代替哈哈),也可以播放音乐。
在这里插入图片描述
以下是一些语音文件,文件通过U盘放在主板上的外部SPI Flash,通过文件系统读取这些音频文件进行播放。
在这里插入图片描述

作品演示及源码

视频我放在B站了,因为手上没有蜂鸣器,演示效果不是太好。以下是视频链接:
演示视频

源码已经放在gitee上了,因为只花了一天时间写这个,会比较乱。以下是gitee地址:
gitee仓库

后期规划

这个东西我是打算继续做下去的,还有很多可以完善的地方,主要有以下几个方向:
(1)最近也在学emWin,后期准备把LCD和触摸屏给用上,给它一个漂亮交互界面,心之天气上有很多数据可以获取到,例如风速、降雨量、湿度等等,这些都是可以实现的。
(2)目前只能通过蓝牙对设备进行控制,没办法远程控制,后期可以把远程这部分实现,这样可以通过手机在办公室就能控制设备。
(3)上班出门时,扔垃圾和天气预报需要手动设置时间,不是很合理,可以考虑使用红外模块检测用户是否出门,来进行提醒。

对的,我也发现这个问题,请问楼主解决了吗?

默认用的是软件IIC,官方有相应的例子,可以看看:,这个链接也可以参考下https://blog.csdn.net/qq_22902757/article/details/106036982

    本帖最后由 谭卓琳 于 2020-4-4 16:10 编辑


帖子不好排版,这里直接贴一个CSDN链接吧,一样的。

这个工具支持Win7吗?我在win7上用不了1.png

一、开发环境:

1、硬件环境:野火霸道F103开发板

2、软件环境:RT-Thread Nano 源码、ST 3.5固件库、MDK5.25

二、实验目标:
熟悉按键的检测,熟悉线程的创建和删除。

三、实验思路:



1、首先需要进行按键检测,RT-Thread软件包里面有一些比较好用的按键软件包,不过我这里使用的是何老师提供的按键检测软件包。可以实现基本的消抖,短按,长按。还需要新建一个定时器,1ms检测一次按键,我使用的是RT-Thread的软件定时器。当检测到按键变化的时候,就通过消息队列发送消息,通知按键处理线程对不通的动作进行处理。


2、创建一个按键处理线程,接收消息队列的按键动作,创建或者删除线程。


3、为了方便观察,我这里动态创建和删除的是一个1s间隔的蜂鸣器响的线程。


四、实验步骤:

1、添加按键检测软件包,主要就是修改GPIO引脚为自己开发板的引脚,根据自己的板子电路设置按键按下是高电平还是低电平。

/****************************************************************************************/
//用户添加自定义接口变量

/****************************************************************************************/
//-------------------------------------------------------------------------------
// Key1 按键引脚
#define GPIO_CLK_Key1 RCC_APB2Periph_GPIOA
#define GPIO_Pin_Key1 GPIO_Pin_0
#define GPIO_Mode_Key1 GPIO_Mode_IN_FLOATING
#define GPIO_Key1 GPIOA
#define Key1In GPIO_ReadInputDataBit(GPIO_Key1, GPIO_Pin_Key1)
//-------------------------------------------------------------------------------
// Key2 按键引脚
#define GPIO_CLK_Key2 RCC_APB2Periph_GPIOC
#define GPIO_Pin_Key2 GPIO_Pin_13
#define GPIO_Mode_Key2 GPIO_Mode_IN_FLOATING
#define GPIO_Key2 GPIOC
#define Key2In GPIO_ReadInputDataBit(GPIO_Key2 , GPIO_Pin_Key2)
/****************************************************************************************/
//用户添加自定义按键接口

/****************************************************************************************/


//-----------------------------------------------------------------------------------------------
// Key按键按下时的电平,=0,按下时为低电平;=1,按下时为高电平
#define KeyPressedLevel 1
2、创建一个按键处理线程,接受消息队列的消息,根据消息进行按键处理。PS:按键的检测是通过软件定时器,以1ms为周期对按键进行检测。

/*******************************************************************************************************
** 函数: button_timer_timeout 1ms周期调用底层按键检测函数
**------------------------------------------------------------------------------------------------------
** 参数: void
** 返回: void
********************************************************************************************************/
void button_timer_timeout(void *parameter)
{
ButtonProj();
}

/*******************************************************************************************************
** 函数: button_thread_entry,获取按键事件并进行处理
**------------------------------------------------------------------------------------------------------
** 参数: void
** 返回: void
********************************************************************************************************/
void button_thread_entry(void *parameter)//用户消息处理入口函数
{
rt_err_t uwRet = RT_EOK;
uint8_t r_queue;//用于接收msg_mq消息队列信息
rt_timer_t button_timer;//用于定时调用按键检测函数
rt_thread_t tid = RT_NULL;//用于记录动态创建和删除线程的指针

button_mq = rt_mq_create("button_mq", //消息队列名字
1, //消息的最大长度, bytes
256, //消息队列的最大容量(个数)
RT_IPC_FLAG_FIFO //如果有多个线程等待此消息,则使用先进先出的方式进行线程调用
);
if(button_mq != RT_NULL)
rt_kprintf("button_mq create success\n\n");

ButtonInit();//按键硬件接口初始化

button_timer = rt_timer_create("button_timer", //定时器名字
button_timer_timeout, //定时器回调函数
RT_NULL, //参数
1, //1ms检测一次按键
RT_TIMER_FLAG_PERIODIC | //周期调用
RT_TIMER_FLAG_SOFT_TIMER ); //软件定时

if (button_timer != RT_NULL)
rt_timer_start(button_timer);

while(1)
{ //获取队列信息
uwRet = rt_mq_recv(button_mq,
&r_queue,
sizeof(r_queue),
RT_WAITING_FOREVER
);
if(RT_EOK == uwRet )
{
switch(r_queue)//根据接收到的消息内容分别进行处理
{
case KEY1_DOWN:rt_kprintf("Receive message:KEY1(PA.0) Down\n\n");
if( tid == RT_NULL )
{
tid = beep_task_create();
if( tid != RT_NULL )
rt_kprintf("蜂鸣器线程创建成功!");
else
rt_kprintf("蜂鸣器线程创建失败!");
}
else
rt_kprintf("蜂鸣器线程已存在!");

break;
case KEY1_UP:rt_kprintf("Receive message:KEY1(PA.0) Up\n\n");break;
case KEY1_LONG:rt_kprintf("Receive message:KEY1(PA.0) LongPressed Down\n\n");
break;
case KEY2_DOWN:rt_kprintf("Receive message:KEY2(PC.13) Down\n\n");
if( tid != RT_NULL )
{
if( rt_thread_delete(tid) == RT_EOK )
{
rt_kprintf("蜂鸣器线程删除成功!");
tid = RT_NULL;
}
else
rt_kprintf("蜂鸣器线程删除失败!");
}
else
{
rt_kprintf("蜂鸣器线程还未创建!");
}
break;
case KEY2_UP:rt_kprintf("Receive message:KEY2(PC.13) Up\n\n");break;
case KEY2_LONG:rt_kprintf("Receive message:KEY2(PC.13) LongPressed Down\n\n");break;
default: rt_kprintf("No button Message!\n\n");break;
}

}
else
{
rt_kprintf("数据接收错误,错误代码:0x%lx\n\n",uwRet);
}
}
}

四、实验结果:
无标题.png

一、开发环境:

1、硬件环境:野火霸道F103开发板

2、软件环境:RT-Thread Nano 源码、ST 3.5固件库、MDK5.25

二、实验目标:
使用消息队列的方式实现串口2的数据接收

三、实验思路:

思路1、使用串口接收中断,将一个一个的字节数据发送给消息队列。开一个线程专门用来取出消息队列的数据(通过超时的方式)。如果获取消息队列的数据时,返回超时,说明串口空闲了,判断下有没有收到数据,有的话就进行数据解析。
PS:这种方式下,如果发送方发送的数据间隔小于超时间隔,会导致接收消息队列永远不会超时,也就是说数据会得不到处理。

思路2、使用串口接收中断和空闲中断,当串口有数据过来的时候,将数据存到全局缓冲区中。当空闲的时候,触发空闲中断,这时候可以将数组的首地址和接受到的数据长度发送给消息队列。开一个线程专门用来接收消息队列的数据(永久等待的方式),如果消息队列获取到消息,就将消息拷贝出来,清空全局缓冲区,然后进行数据解析。
PS:这种方式下,如果数据在处理线程中还没有拷贝完成,串口又来数据了,就可能导致数据被破坏。

两种方式都各有利弊,这是我自己的思路,感觉应该还有更好的处理方式的。

我使用的是第一种方式来进行实现。

四、实验步骤:

1、添加串口驱动,初始化串口,设置串口为接收中断

void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

//GPIO USART2-Tx
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//GPIO USART2-Rx
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//USART2-Config
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);

//NVIC-Config
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

//INT-Select
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
//USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//添加串口空闲中断使能,请不要使用USART_IT_RXNE|USART_IT_IDLE,记住分开写两条语句

USART_Cmd(USART2, ENABLE);

USART_ClearFlag(USART2, USART_FLAG_TC);//清发送完成标志位
}

void USART2_IRQHandler(void)
{
u8 data;

if( USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET )
{
data = (uint8_t)USART_ReceiveData(USART2);

rt_mq_send(usart2_recv_msg_mq, (void*)&data, 1);/* 将接收到的数据发送线程 */
USART_ClearFlag(USART2, USART_FLAG_RXNE);
}

if( USART_GetFlagStatus(USART2, USART_FLAG_ORE) == SET )
{
USART_ClearFlag(USART2, USART_FLAG_ORE);
}
}


2、创建一个消息队列,每一个消息的大小为1字节,消息队列总大小为256字节。
 usart2_recv_msg_mq = rt_mq_create("usart2_recv_msg_mq",		/* 创建消息队列用于串口2数据接收 */
1, /* 消息队列最大长度 byte */
256, /* 消息的数量 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待此消息队列,采用先进先出的方式进行线程切换 */
if( usart2_recv_msg_mq != RT_NULL )
rt_kprintf("usart2_recv_msg_mq 消息队列创建成功!\n");
else
rt_kprintf("usart2_recv_msg_mq 消息队列创建失败!\n");

3、创建一个线程,专门用来接收来自串口消息队列的数据。
void usart2_recv_thread_entry(void *parameter)
{
uint8_t *buffer = RT_NULL;/* 用于分配存储消息的内存指针 */
uint8_t data;
uint16_t index = 0;
rt_err_t res;

buffer = rt_malloc(256);/* 分配一个256字节的缓冲区 */

if( buffer == RT_NULL )
{
rt_kprintf("usart2_recv_thread_entry 内存分配失败!\n");
return ;
}

while(1)
{
res = rt_mq_recv(usart2_recv_msg_mq, (void*)&data, 1, 40);/* 从消息队列中接收消息到 data */

if( RT_EOK == res )/* 有数据过来,将数据进行保存 */
{
index = index >= 254 ? 254 : index;
buffer[index++] = data;
}
else if( -RT_ETIMEOUT == res )/* 超出40ms没有接收到数据,说明串口空闲了 */
{
if( index > 0 )/* 如果缓冲区中有数据,进行数据处理 */
{
buffer[index] = '\0';

rt_kprintf("Usart2 Recv Buffer : %s \n", buffer);

if( strstr((char *)buffer, "BeepOn") != NULL )
{
BeepOn();
}
if( strstr((char *)buffer, "BeepOff") != NULL )
{
BeepOff();
}

index = 0;
}
}
else if( -RT_ERROR == res )
{
rt_kprintf("接收消息错误!");
}
}
}
五、实验结果:
无标题.png









    本帖最后由 谭卓琳 于 2020-3-29 23:44 编辑


一、开发环境:

1、硬件环境:野火霸道F103开发板

2、软件环境:RT-Thread Nano 源码、ST 3.5固件库、MDK5.25

二、实验步骤:

1、准备一个裸机工程,添加RT-Thread Nano 源码、移植FinSH组件,实现RT_kprintf打印函数。以上操作过程按照老师的视频一步一步进行操作就行啦,这里只把移植以后的工程目录贴上。
[img=110,0][/img]
2、添加看门狗驱动和喂狗函数。
void IWDG_Config(uint8_t prv ,uint16_t rlv)
{
// 使能 预分频寄存器PR和重装载寄存器RLR可写
IWDG_WriteAccessCmd( IWDG_WriteAccess_Enable );

// 设置预分频器值
IWDG_SetPrescaler( prv );

// 设置重装载寄存器值
IWDG_SetReload( rlv );

// 把重装载寄存器的值放到计数器中
IWDG_ReloadCounter();

// 使能 IWDG
IWDG_Enable();
}

// 喂狗
void IWDG_Feed(void)
{
// 把重装载寄存器的值放到计数器中,喂狗,防止IWDG复位
// 当计数器的值减到0的时候会产生系统复位
IWDG_ReloadCounter();
}

3、创建喂狗线程,这里使用的是何老师经过抽象以后的线程创建方式,使的代码看起来比较简洁。
TaskStruct TaskThreads[] = {

{"iwdg_thread", iwdg_thread_entry, RT_NULL, 512, 15, 10},/* 在这里添加喂狗线程 */
{"led_thread", led_thread_entry, RT_NULL, 512, 15, 10},
{"msg_process_thread", msg_process_thread_entry, RT_NULL, 512, 2, 10},
{"usart2_recv_thread", usart2_recv_thread_entry, RT_NULL, 512, 2, 10},

/*********************************************************/
//用户添加线程参数
//例如:{线程名字,线程入口函数,线程入口函数参数,线程栈大小,线程的优先级,线程时间片},

{" ", RT_NULL, RT_NULL, RT_NULL, RT_NULL, RT_NULL},

};

4、实现喂狗线程入口函数,实现喂狗操作。
void iwdg_thread_entry(void *parameter)
{
IWDG_Config(IWDG_Prescaler_64 ,1250); /* IWDG 2s 超时溢出 */

while(1)
{
IWDG_Feed();
rt_thread_mdelay(1000);/* 每隔 1s 进行喂狗操作 */
}
}

5、下载程序,工作一切正常。
[img=110,0]https://www.rt-thread.org/qa/forum.php?mod=image&aid=14325&size=300x300&key=749f9a8e8139141b&nocache=yes&type=fixnone[/img]


6、将喂狗时间改为2200ms,看门狗溢出,单片机复位,说明之前的喂狗操作是有效的。
[img=110,0]https://www.rt-thread.org/qa/forum.php?mod=image&aid=14326&size=300x300&key=d284b7ea08dd5c36&nocache=yes&type=fixnone[/img]

三、总结。


第一次使用RTOS,以前代码都是裸机操作。今天是礼拜天,刚好有时间就把第一周的视频都看了,实验也都动手实操了一遍(最后的按键去抖那部分还没实验完哈哈哈),这个培训我最大的收获其实感觉并不仅仅是RTOS,更有触动的是视频里面的一些思路,感觉挺实用的(也可能我是小白),例如所有的头文件都是通过config.h进行管理,这样源文件就不会包含一大堆的头文件了,通过宏定义 EXT 为 extern 和 EXT 为空,来避免全局变量的冲突,并且还把创建线程给封装了一下,使代码更加简洁,忍不住夸一波666呀。希望小伙伴们也能好好学习,天天向上哈哈哈。


CYJ3IDP]M_BQ{WTI]9(8O49.png
@2_6VKOY9VE1)LP8PD9BTPD.png
7TF)]8}`}N6MOLYTEOGP~QA.png

回到
顶部

发布
问题

投诉
建议