RT-Thread系列
Keil模拟器 STM32F103上手指南学习笔记
RT-Thread Studio快速上手
RT-Thread Nano入门学习笔记(1)
项目实战:快速打造一个桌面mini网络时钟
使用内置Git插件管理项目
基本知识:
程序修改:
首先是要加入外部中断的两个文件:
然后进行寄存器和引脚的相应修改
然后添加线程,变量并修改增加相应程序,运行仿真得到如下结果:
仿真结果显示正确
将程序烧录到板子上,连接SecureCRT,按键并观察现象:
可观看视频
修改EXTI2_IRQHandler()
和EXTI3_IRQHandler()
,如下图:
void EXTI2_IRQHandler (void)
{
MSG_TYPE msg = MSG_KEY2_PRESS;
if(EXTI_GetITStatus(EXTI_Line2) == SET )
{
//用户代码
rt_mq_send(msg_mq,&msg,sizeof(msg));
//--------------------------------
EXTI_ClearFlag(EXTI_Line2);
}
}
void EXTI3_IRQHandler (void)
{
MSG_TYPE msg = MSG_KEY1_PRESS;
if(EXTI_GetITStatus(EXTI_Line3) == SET )
{
//用户代码
rt_mq_send(msg_mq,&msg,sizeof(msg));
//--------------------------------
EXTI_ClearFlag(EXTI_Line3);
}
}
创建函数msg_process_thread_entry(void *parameter)
如下:
void msg_process_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
uint8_t r_queue;
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 pressed\n\n");break;
case MSG_KEY2_PRESS:rt_kprintf("Receive message:KEY2(PE.2) is pressed\n\n");break;
default:rt_kprintf("No Message!\n\n");break;
}
}
else
{
rt_kprintf("数据接受错误,错误代码为:0x%lx\n",uwRet);
}
}
}
在文件config.h里添加
typedef enum
{
MSG_NULL = 0,
//添加用户消息常量,如MSG_XXX,
MSG_KEY1_PRESS,
MSG_KEY2_PRESS,
MSG_NUM
}MSG_TYPE;
可通过仿真之后右击相关变量,添加窗口,得到其数据类型,例如:msg为uchar型变量,如下图:
Task.h修改如下:增加TaskStruct结构体
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修改如下:建立动态线程dynamic_thread
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}
};
void TaskInit(void)
{
uint8_t TaskThreadIndex = 0;
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");
}
msg_mq = rt_mq_create("msg_mq", //消息队列名字
32, //消息的最大长度, bytes
10, //消息队列的最大容量(个数)
RT_IPC_FLAG_FIFO //队列模式 FIFO
);
if(msg_mq != RT_NULL)
{
rt_kprintf("消息队列key_mq创建成功\n\n");
}
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;
}
}
编译结果如下:
仿真结果如下:
加入定时器和按键的相关文件:
下载好程序到板子上,并在SecureCRT上查看list_thread,如下图:
按下板子上的按键进行测试
测试结果符合预期
添加Adc.c和Adc.h文件
编译成功
下载好程序到板子上:
连接SecureCRT,可以看到大概每两秒打印一次,如下图:
两秒:
修改Button.c
void button_thread_entry(void *parameter)//用户消息处理入口函数
{
rt_err_t uwRet = RT_EOK;
uint8_t r_queue;//用于接收msg_mq消息队列信息
uint32_t AdcSoftTimerPeriod = 0;//存储ADC软件定时器周期值
uint8_t AdcSoftTimerStatus = 0;//ADC软件定时器状态,=0,停止;=1,启动
button_mq = rt_mq_create("button_mq", //消息队列名字
1, //消息的最大长度, bytes
256, //消息队列的最大容量(个数)
RT_IPC_FLAG_FIFO //队列模式 FIFO
);
if(button_mq != RT_NULL)
rt_kprintf("button_mq create success\n\n");
ButtonInit();//按键硬件接口初始化
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("\nReceive message:KEY1(PE.3) Down\n");
if(AdcSoftTimerStatus == 0)
{
rt_kprintf("ADCProcessSoftTimer Start!\n\n");
rt_timer_start(ADCProcessSoftTimer);
AdcSoftTimerStatus = !AdcSoftTimerStatus;
}
else
{
rt_kprintf("ADCProcessSoftTimer Stop!\n\n");
rt_timer_stop(ADCProcessSoftTimer);
AdcSoftTimerStatus = !AdcSoftTimerStatus;
}
break;
case KEY1_UP:break;
case KEY1_LONG:break;
case KEY2_DOWN:rt_kprintf("\nReceive message:KEY2(PE.2) Down\n");
rt_timer_control(ADCProcessSoftTimer, RT_TIMER_CTRL_GET_TIME, (void *)&AdcSoftTimerPeriod);
AdcSoftTimerPeriod += 2*RT_TICK_PER_SECOND;
rt_timer_control(ADCProcessSoftTimer, RT_TIMER_CTRL_SET_TIME, (void *)&AdcSoftTimerPeriod);
rt_kprintf("ADCProcessSoftTimer Period is Added,Now is:%d\n\n",AdcSoftTimerPeriod /RT_TICK_PER_SECOND);
break;
case KEY2_UP:break;
case KEY2_LONG:break;
case KEY3_DOWN:rt_kprintf("\nReceive message:KEY3(PE.4) Down\n");
rt_timer_control(ADCProcessSoftTimer, RT_TIMER_CTRL_GET_TIME, (void *)&AdcSoftTimerPeriod);
if( AdcSoftTimerPeriod > 2*RT_TICK_PER_SECOND )
{
AdcSoftTimerPeriod -= 2*RT_TICK_PER_SECOND;
rt_timer_control(ADCProcessSoftTimer, RT_TIMER_CTRL_SET_TIME, (void *)&AdcSoftTimerPeriod);
rt_kprintf("ADCProcessSoftTimer Period is subtracted,Now is:%d\n\n",AdcSoftTimerPeriod /RT_TICK_PER_SECOND);
}
else
rt_kprintf("ADCProcessSoftTimer Period Low Limited!\n\n");
break;
case KEY3_UP:break;
case KEY3_LONG:break;
default :break;
}
}
else
{
rt_kprintf("数据接收错误,错误代码:0x%lx\n\n",uwRet);
}
}
}
编译成功
下载程序到板子上:
连接SecureCRT成功后,定时器还没有开启,等到按下KEY1时就开启了,如下图:
再按下KEY1,可以看到定时器停下了:
如果RT_USING_TIMER_SOFT定义为1,则使用了软件定时器,反之则无
rt_system_timer_init主要是对列表进行初始化
停止即把定时器删掉即可:
演示无符号相减
修改SysTick.c
添加rt_hw_us_delay(rt_uint32_t us)
void rt_hw_us_delay(rt_uint32_t us)
{
rt_uint32_t delta;
rt_uint32_t current_delay;
/* 获得延时经过的tick数 */
us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));
/* 获得当前时间 */
delta = SysTick->VAL;
/* 循环获得当前时间,直到达到指定的时间后退出循环 */
do
{
if ( delta > SysTick->VAL )
current_delay = delta - SysTick->VAL;
else
/* 延时跨越了一次OS tick的边界时的处理 */
current_delay = SysTick->LOAD + delta - SysTick->VAL;
} while( current_delay < us );
}
添加DS18B20驱动,如下图:添加BspDs18b20.h和BspDs18b20.c文件
BspDs18b20.h文件:
编译程序:
连接实物,下载程序到板子上,实物图如图:
连接SecureCRT,输入list_thread查看线程,可以看到GetTemperature
然后输入GetTemperature命令可以查看温度,如下图
查看电脑的ip地址:
打开两个USR-TCP232-Test 串口转网络调试助手,一个做服务端,一个做客户端,完成连接和发送!,如下图所示
由于手机上找不到有人网络助手APP,暂时先不做手机与电脑的通信,但步骤是差不多的。
进入下一步:
将EPS8266模块通过USB-TTL模块和电脑连接起来,实物图如图所示:
打开串口助手:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjbu1Sk4-1625289573817)(C:\Users\xiaoyan\AppData\Roaming\Typora\typora-user-images\image-20210702175530356.png)]
可以看到显示OK,说明是存在的~
学会使用以下指令:
AT //模块测试指令,判断模块是否存在
AT+RST //该步可用,可不用
AT+CWMODE=1 //1:STATION; 2:AP; 3:AP+STA
AT+CWJAP="MI6","12345678" //接入热点,填入热点名称及对应密码
AT+CIFSR //查询模块IP地址,该步可用,可不用(若不需要知道模块IP地址,则省)
AT+CIPSTART="TCP","183.230.40.33",80 //填入TCP Server的IP地址及端口号
AT+CIPMODE=1 //开启透传模式
AT+CIPSEND //开始发送数据,该命令须在透传模式下使用
/*****以下使用HTTP协议与ONENET通信*****/
POST /devices/35622896/datapoints?type=5 HTTP/1.1
api-key:n4ZM7QyXhH1oid=9GAB96YltclE=
Host:api.heclouds.com
Content-Length:24
,;Temperature,46;Volt,13
/****************************************/
当模块处于透传模式时,发送的均为数据,即使某些数据以AT开头,也仍然为数据;
退出透传模式,请发送 +++ (注意不要加回车换行,编程时须注意);退出透传模式之后
才能使用AT命令
AT+CIPCLOSE //关闭连接
注意以上AT指令 均须加回车换行(\r\n),退出透传指令+++(不要加回车换行)
使用图示:
AT+RST //该步可用,可不用
AT+CWMODE=1 //1:STATION; 2:AP; 3:AP+STA
AT+CIFSR //查询模块IP地址,该步可用,可不用(若不需要知道模块IP地址,则省)
注册OneNet网站并创建HTTP产品如下:
新增一台设备:
添加两个数据流:
进入下一步:
下载安装并注册花生壳:
//添加串口空闲中断使能,请不要使用USART_IT_RXNE|USART_IT_IDLE,记住分开写两条语句
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
记得在config.h中添加以下头文件:
#include "stdarg.h"
#include "stdbool.h"
#include "BspEsp8266.h" //ESP8226 WIFI模块头文件
添加USART_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 );
}
}
定义之后要记得在头文件中声明
void UsartSendByte(USART_TypeDef* USARTx,uint8 ch);//去掉static
void USART_printf ( USART_TypeDef * USARTx, char * Data, ... );
添加BspEsp8266.c和BspEsp8266.h文件
PB.10 ----------------- Usart3 Tx /ESP8266 WIFI
PB.11 ----------------- Usart3 Rx /ESP8266 WIFI
PD.0 ----------------- ESP8266 WIFI RST PIN
PD.1 ----------------- ESP8266 WIFI EN(CH) PIN
/*
* 函数名:ESP8266_Cmd
* 描述 :对WF-ESP8266模块发送AT指令
* 输入 :cmd,待发送的指令
* reply1,reply2,期待的响应,为NULL表不需响应,两者为或逻辑关系
* waittime,等待响应的时间
* 返回 : 1,指令发送成功
* 0,指令发送失败
* 调用 :被外部调用
*/
bool ESP8266_Cmd ( char * cmd, char * reply1, char * reply2, u32 waittime )
{
strEsp8266_Fram_Record .InfBit .FramLength = 0; //从新开始接收新的数据包
macESP8266_Usart ( "%s\r\n", cmd );
if ( ( reply1 == 0 ) && ( reply2 == 0 ) ) //不需要接收数据
return true;
rt_thread_mdelay(waittime);
strEsp8266_Fram_Record .Data_RX_BUF [ strEsp8266_Fram_Record .InfBit .FramLength ] = '\0';
if ( ( reply1 != 0 ) && ( reply2 != 0 ) )
return ( ( bool ) strstr ( strEsp8266_Fram_Record .Data_RX_BUF, reply1 ) || ( bool ) strstr ( strEsp8266_Fram_Record .Data_RX_BUF, reply2 ) );
else if ( reply1 != 0 )
return ( ( bool ) strstr ( strEsp8266_Fram_Record .Data_RX_BUF, reply1 ) );
else
return ( ( bool ) strstr ( strEsp8266_Fram_Record .Data_RX_BUF, reply2 ) );
}
/*
* 函数名:ESP8266_JoinAP
* 描述 :WF-ESP8266模块连接外部WiFi
* 输入 :pSSID,WiFi名称字符串
* :pPassWord,WiFi密码字符串
* 返回 : 1,连接成功
* 0,连接失败
* 调用 :被外部调用
*/
bool ESP8266_JoinAP ( char * pSSID, char * pPassWord )
{
char cCmd [120];
sprintf ( cCmd, "AT+CWJAP=\"%s\",\"%s\"", pSSID, pPassWord );
return ESP8266_Cmd ( cCmd, "OK", NULL, 5000 );
}
添加WifiCmdTest.c和WifiCmdTest.h文件,如下图:
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.);