基于rt_thread的pwm设备应用实例

PWM概述

我们即将创建一个会变换颜色的呼吸灯的程序,在编写程序之前我觉得有必要先稍微讲解一下什么是pwm。

pwm的全称是Pulse Width Modulation,翻译过来就是脉冲宽度调制。

以上就是pwm的原理示意图。在T1这个时间段中属于高电平输出,在T2这个时间段中属于底电平输出。高电平在整个周期(T1+T2)中所占时间的比例就是占空比,通过对占空比的控制我们可以输出一段幅值相等的脉冲。

PWM控制亮度的原理

我们平时调节手机屏幕和灯的亮暗就是靠pwm来进行控制的,通过对占空比的控制我们可以让led灯进行快速的闪烁,这个闪烁因为过快导致我们的肉眼根本无法识别出来,在不停的闪烁中如果灭的持续时间长那么我们就会觉得屏幕变暗了,如果亮的时间长我们就会觉得屏幕变亮了。

编程思路

首先贴上本次要使用的函数:

rt_device_find()

rt_pwm_set()

rt_pwm_enable()

rt_pwm_disable()

函数 描述
rt_device_find() 根据 PWM 设备名称查找设备获取设备句柄
rt_pwm_set() 设置 PWM 周期和脉冲宽度
rt_pwm_enable() 使能 PWM 设备
rt_pwm_disable() 关闭 PWM 设备

通过不停对占空比进行有序的改变可以让灯的亮度由暗变亮再让灯由亮变暗,有两种方法可以完成这种操作,一个是使用for或者while循环,一个就是使用数组,在接下来的实例中我们是使用数组进行有序控制的。

代码

首先在这儿声明一下,这里使用的开发板以低电平输出亮,高电平输出暗,开发板中关于led的原理图如下:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define pwm        "pwm3"
int jun[300];//声明数组
int a=0;
/* 将占空比的值赋给数组 */
void pwm_sat8(){
   for(int i=100000;i>=0;i=i-1000){
  jun[a]=i;
   a++;
}
  
   for(int i=0;i<=100000;i=i+1000){
  jun[a]=i;
   a++;
}

  a=0;
  struct rt_device_pwm *pwm_dev;//设备句柄      
  pwm_dev = (struct rt_device_pwm *)rt_device_find(pwm);//查找设备
/* 完成红、绿、蓝亮度的依次变化 */
  while(1){
    
  for(a=0;a<=201;a++){
   rt_pwm_set(pwm_dev,2,100000,jun[a]);
   rt_pwm_enable(pwm_dev,2);
   rt_thread_mdelay(20);
  }
  
  for(a=0;a<=201;a++){
   rt_pwm_set(pwm_dev,3,100000,jun[a]);
   rt_pwm_enable(pwm_dev,3);
   rt_thread_mdelay(20);
  }
  
  for(a=0;a<=201;a++){
   rt_pwm_set(pwm_dev,4,100000,jun[a]);
   rt_pwm_enable(pwm_dev,4);
   rt_thread_mdelay(20);		
  }
  
    }
  
      }

MSH_CMD_EXPORT(pwm_sat8, pwm sample)

首先,我们定义了一个名字为jun,大小为300的数组。

我们在这里设置的周期是100000纳秒(也就是0.1ms),因为亮度的大小范围是0%——100%,所以我们将占空比从100000,每次以减1000的方式赋值给数组,再从0,以每次加1000的方式赋值给数组,这样子我们就得到了{100000,99000,98000…98000,99000,100000}的数组。

接着使用rt_device_find()获取设备句柄,在获取设备句柄之前我们可以看到有这么一行代码:

struct rt_device_pwm *pwm_dev;

这行代码我们先创建了一个结构体,并给这个结构体设置了一个指针。

因为rt_device_find()返回的是一个句柄所以我们要先强制类型转换,然后才能把返回的句柄赋给指针。

while(1){
  
for(a=0;a<=201;a++){
 rt_pwm_set(pwm_dev,2,100000,jun[a]);
 rt_pwm_enable(pwm_dev,2);
 rt_thread_mdelay(20);
}

for(a=0;a<=201;a++){
 rt_pwm_set(pwm_dev,3,100000,jun[a]);
 rt_pwm_enable(pwm_dev,3);
 rt_thread_mdelay(20);
}

for(a=0;a<=201;a++){
 rt_pwm_set(pwm_dev,4,100000,jun[a]);
 rt_pwm_enable(pwm_dev,4);
 rt_thread_mdelay(20);		
}

  }

以上代码就是实现交替变换颜色并且安照顺序调节亮度的代码。我们抽取第一段分析一下:

for(a=0;a<=201;a++){
   rt_pwm_set(pwm_dev,2,100000,jun[a]);
   rt_pwm_enable(pwm_dev,2);
   rt_thread_mdelay(20);
  }

通过rt_pwm_set()可以设置我们需要使用的pwm设备和需要使用的通道(不理解什么是通道?看看我刚发的led原理图),然后设置周期为100000,最后设置占空比,在上文中我们已经讲过我们是使用数组进行占空比设置的,所以设置为jun[a],通过for循环中的判断和更新变量来完成占空比从100000到0再从0到100000的变化。

在设置pwm设备后我们要使能pwm设备,最后加上延时函数rt_thread_mdelay(),这里我们设置延时为0.02毫秒。

*注意

因为本次使用的开发板是以低电平为输出led亮的,所以当你使用env工具配置好pwm设备后它会默认亮起来,为了解决这个问题我们可以在mian()函数中编写如下代码:

int main(){
struct rt_device_pwm *pwm_dev;      
  pwm_dev = (struct rt_device_pwm *)rt_device_find("pwm3");
  rt_pwm_set(pwm_dev,2,100000,100000);
  rt_pwm_enable(pwm_dev,2);
  rt_pwm_set(pwm_dev,3,100000,100000);
  rt_pwm_enable(pwm_dev,3);
  rt_pwm_set(pwm_dev,4,100000,100000);
  rt_pwm_enable(pwm_dev,4);
}

这样子我们就能在开始的时候把它给关闭了。

学习辛苦了,看点色图放松一下吧(/龇牙)

 

个人创建基于rt_thread的项目

在学习了中断函数后突发奇想想自己创建一个用中断函数来控制led灯的闪烁,说干就干,期望达到的效果如下:

按下key1后蜂鸣器发出声音同时led以绿,蓝,红的顺序进行闪烁;按下key2后蜂鸣器停止发出声音同时led灯熄灭。

在经过一番操作后代码如下:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define KEY0_PIN_NUM 0
#define BEEP_PIN_NUM 8

int jb,junbian;
/*完成对led的初始化*/
void led_start(){
  rt_pin_mode(16,PIN_MODE_OUTPUT);
  rt_pin_write(16,PIN_HIGH);
  rt_pin_mode(17,PIN_MODE_OUTPUT);
  rt_pin_write(17,PIN_HIGH);
  rt_pin_mode(21,PIN_MODE_OUTPUT);
  rt_pin_write(21,PIN_HIGH);	
}
/*完成对蜂鸣器的初始化*/
void jun_ready(){

  rt_pin_mode(8,PIN_MODE_OUTPUT);
  rt_pin_write(8,PIN_HIGH); 

}
/*完成led的闪烁控制*/
void jun_on(){
  
  rt_pin_write(16,PIN_LOW);
  rt_thread_delay(50);
  rt_pin_write(16,PIN_HIGH); 
  
  rt_pin_write(17,PIN_LOW);
  rt_thread_delay(50);
  rt_pin_write(17,PIN_HIGH);
  
  rt_pin_write(21,PIN_LOW);
  rt_thread_delay(50);
  rt_pin_write(21,PIN_HIGH);	
  
}
/*完成关闭led的控制*/
void jun_off(){
  rt_pin_write(8,PIN_LOW);
  rt_pin_write(16,PIN_HIGH);
  rt_pin_write(17,PIN_HIGH);
  rt_pin_write(21,PIN_HIGH);
}

void jun_start(){
  led_start();//调用led初始化函数
/*将key1和key2设置成上拉输入*/
  rt_pin_mode(0,PIN_MODE_INPUT_PULLUP );
  rt_pin_mode(45,PIN_MODE_INPUT_PULLUP );
  while(1){
  rt_pin_attach_irq(0,PIN_IRQ_MODE_FALLING,jun_ready,RT_NULL);//绑定引脚中断回调函数
  rt_pin_irq_enable(0,PIN_IRQ_ENABLE);//使能引脚中断
  
  jb=rt_pin_read(8);//读取蜂鸣器的电平
  
  if(jb==PIN_HIGH){
    while(jb==PIN_HIGH){
    jun_on();
  rt_pin_attach_irq(45,PIN_IRQ_MODE_FALLING,jun_off,RT_NULL);
  rt_pin_irq_enable(45,PIN_IRQ_ENABLE);
      junbian=rt_pin_read(8);
      if(junbian==PIN_LOW){
        break;
      }
      
    }
  }
}
}

MSH_CMD_EXPORT(jun_start,sat8 is the best)

现在说一下项目设计的思路,首先在完成对于引脚的初始化后我们首先要做的事就是实现闪烁功能,通过延时函数rt_thread_delay()我们可以轻易做到闪烁的功能。有关本项目所使用到的函数在这里我都写了注释:https://www.mdrfb.com/2019/09/03/examples-of-functions-in-rt_thread/

接下来就要解决用按钮控制的问题了,根据中断回调函数我们可以让程序在读到这一行的时候执行中断并作出相应的操作,

rt_pin_attach_irq(0,PIN_IRQ_MODE_FALLING,jun_ready,RT_NULL);//绑定引脚中断回调函数 
rt_pin_irq_enable(0,PIN_IRQ_ENABLE);//使能引脚中断

这两行实现了使用key1按钮来让蜂鸣器器响起来的功能因为蜂鸣器要和led灯一起闪烁所以我们需要时刻对蜂鸣器进行电平检测,当电平被检测为高时就代表我们的led就要开始闪起来了,

jb=rt_pin_read(8);//读取蜂鸣器的电平

这一行代码完成了对于蜂鸣器电平的检测,再根据if函数来执行检测到蜂鸣器电平为high时led开始闪烁的功能。

因为我们还要通过key2按钮来关闭led和蜂鸣器所以我们还要再打一行中断回调函数和使能引脚中断, 

 rt_pin_attach_irq(45,PIN_IRQ_MODE_FALLING,jun_off,RT_NULL);
 rt_pin_irq_enable(45,PIN_IRQ_ENABLE);

根据上文的思路,当蜂鸣器电平变低时led就要熄灭所以我们又添加了一条检测函数,

junbian=rt_pin_read(8);

再根据if判断语句和c语言中break指令完成对闪烁的中断,也就是跳出while循环,至此我们的操作目标已经都达成了。

在编写此类程序的时候有一点需要注意,有些人在编程时会忽略编译器对于读取程序的顺序,程序在运行时速度非常快所以有时候会发生这种情况,我的程序明明符合逻辑天衣无缝为什么下载到开发板上后功能却不完全或根本没有功能,为了达到随按随停,随按随开的功能我们要在主体函数外边加一个while(1)循环,也就是无限循环,否则就会发生按了按钮灯死活不亮的反应,因为你的程序早就被执行完毕了!

在rt_thread中使用线程来控制跑马灯

线程是rt_thread中一个非常重要的东西,它就好比我们在完成一个任务时我们通常会把任务分成一个个小任务然后一步一步来完成这个大任务。线程就好比我们一步一步做的小任务,所以对线程的掌握在学习rt_thread中是很重要的。

首先先贴上代码:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

static void ledjun(void *param){
  rt_pin_mode(16,PIN_MODE_OUTPUT);//控制引脚输出模式
  
  while(1){
    rt_pin_write(16,PIN_LOW);//控制引脚输出低电平
    rt_thread_mdelay(500);//延时50ms
    rt_pin_write(16,PIN_HIGH);
    rt_thread_mdelay(500);
  }
}
static void sat8(void){
rt_thread_t tid=rt_thread_create(
 "led",     //给予线程名字
  ledjun,   //设置入口函数
  RT_NULL,  //函数输入参数为无
  512,      //分配线程栈的大小
  10,       //线程优先级
  10);      //配置时间片参数
  if(tid!=RT_NULL)
    rt_thread_startup(tid);
}                    
MSH_CMD_EXPORT(sat8, sat8 is the best)

如果对rt_thread中函数掌握的比较牢固的话应该理解起来不难,首先完成对跑马灯程序的编写然后开始创建线程。

如何完成跑马灯的编程就不在这儿详细讲了,毕竟就这么点东西注释已经在旁边写好了,如果想详细了解一下跑马灯编程的可以看我之前写的:

在rt_thread中用main函数来实现跑马灯的功能

现在让我们把重点放在线程的创建上:

首先让我们从第一行开始看,我们先是调用了创建线程的函数也就是rt_thread_create,这个函数是rt_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)
{

首先我们要为线程取一个名字,在这里我们取的名字是led;然后设置线程入口函数,因为我们要完成的效果是跑马灯而跑马灯的函数在上方已给出所以我们只要把我们已创建好的跑马灯的函数名字粘贴进去就好;接着我们要设置函数的输入参数,因为在这里我们并没有使用输入参数所以写了RT_NULL,鉴于可能有人不知道输入参数是什么那我们在这里简单的说一下,输入参数就是在函数内部定义的局部变量,举个例子:

void jun(int bian){}

在这里bian就是输入参数;接下来分配线程栈的大小,在这里我们设置成立512k;

下一步就是设置线程的优先级,在rt_thread中数字越小优先级越高,0是最高优先级;最后就是配置时间片参数。

至此我们已经成功将线程初始化完毕了,那么接下来就是要启动这个线程,

if(tid!=RT_NULL) 
  rt_thread_startup(tid);

因为创立线程需要在内存中分配空间以运行这个线程所以在提交完建立线程的请求后会给我们返回一个值以此来告诉我们线程的创建是否成功,如果不成功就返回RT_NULL,所以只要返回的不是RT_NULL我们就启动这个线程,在使用rt_thread_starup这个函数时要注意括号里的参数应该是线程的句柄,什么是句柄?句柄顾名思义是一个柄,它可以拉起一大堆的东西,这个东西就是我们用户自己定义的函数或者其他的一些东西(在这里就是我们自己创建的线程),在某些方面句柄和指针差不多(注意,这里只是差不多,区别还是有的)。

到这里,我们就大功告成了!是不是很开心?让我们来看看我们的劳动成果,咦?为什么我们的开发板一点反应都没有?是不是哪编错了?我可以很负责任的告诉你刚才你做的都没有错,错就错在使用线程时我们要进行串口输出,通过将函数添加到msh命令列表来输出函数,大家可以去下载一个串口调试工具这样在调试时会方便一些。

MSH_CMD_EXPORT(sat8,sat8 is the best)

这段代码中的MSH_CMD_EXPORT就是msh提供的函数以此来将用户函数添加到msh命令列表中,sat8就是我们给跑马灯函数起的名字,sat8 is the best是用户自己定义的,多半情况下你可以把注释或标记写进去,在这里我们写的是sat8 is the best

到这步才算是真正的结束,现在我们把程序编译一下再下载进去led就能闪起来了。

 

 

个人对于rt_thread中函数的理解和例子

//此文章会不间断进行更新,速度为作者学习的速度(捂脸)

1,rt_pin_mode()

此函数是用来完成对引脚的选择和控制输入还是输出模式。

示例:rt_pin_mode(16,PIN_MODE_OUTPUT)

2,rt_pin_write()

此函数是用来完成对引脚的输出高低电平的控制。

示例:rt_pin_write(16,PIN_LOW)

3,rt_mdelay()

此函数是用来将线程挂起来完成延时操作,延时单位为毫秒(ms)。

示例:rt_mdelay(500)

4,rt_delay()

此函数是用来线程挂起来完成延时操作,延时单位为系统滴答,一般我们设置系统时钟滴答的频率为100hz,也就是一个滴答为10ms。

示例:rt_delay(50)

5,rt_thread_create()

此函数是用来创建动态线程用的,具体方式如下:

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)

创建一个动态线程分为这么几步:1,给出线程名字。2,设置入口函数。3,设置函数输入参数。4,定义线程栈的大小。5,设置线程优先级,数字越小优先级越高,0为最高优先级。6,配置线程时间片。

示例:

rt_threat jun=rt_thread_create("junbian",junbian,RT_NULL,512k,5,10)

在示例中假定我们创建的入口函数名称为junbian,RT_NULL所表示的意思为无输入参数。

6,rt_thread_starup()

此函数是用来启动线程的。

示例:rt_thread_starup(jun)

在这里jun假定为线程的句柄。

7,MSH_CMD_EXPORT()

这个严格意义上来说并不是函数而是命令,但是在编程时运用广泛所以也就把它和函数写一块了。可以通过这条命令来来将你所需要执行的线程或函数发送到MSH命令行,然后msh命令列表会通过串口进行输出。

示例:MSH_CMD_EXPORT(sat8,sat8 is the best)

在这里假定sat8为线程名字,逗号后面为用户自己定义的,你可以把注释或者标签写进去,在这里我们写的是sat8 is the best。

8,rt_pin_attach_irq()

此函数是用来绑定引脚中断的回调函数。什么是引脚中断回调函数?当一个引脚猝发某种情况时会将当前任务中断并完成用户指定的另一个任务。

rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
                             void (*hdr)(void *args), void  *args)

这是官方给出的格式,首先确定需要绑定的引脚,再选择触发中断的条件,接着选择触发条件时需要执行的函数,最后设置中断回调函数的参数(不需要时设置为RT_NULL)。

示例:rt_pin_attach_enable(0,PIN_IRQ_MODE_FALLING,jun_ready,RT_NULL)

9,rt_pin_irq_enable()

此函数是用来使能引脚中断的,当我们绑定完引脚中断的回调函数后需要使能引脚中断,也就是给出信号让引脚开始执行中断并执行中断函数。

rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)

这是官方给出的格式,首先设置引脚,再设置状态,状态有两种enable和disable。

示例: rt_pin_irq_enable(0,PIN_IRQ_ENABLE);

在rt_thread中用main函数来实现跑马灯的功能

跑马灯是大部分学习单片机的初学者编的第一个程序,在使用rt_thread嵌入式系统编程时完成对跑马灯的编程可谓是学习rt_thread的第一课,话不多说直接上代码:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

int main(){
rt_pin_mode(16,PIN_MODE_OUTPUT);
  
  while(1){
    rt_pin_write(16,PIN_LOW);
    rt_thread_mdelay(500);
    rt_pin_write(16,PIN_HIGH);
    rt_thread_mdelay(500);
  }
return RT_EOK;
}

这里使用的开发板是野火f103,正常来讲使用rt_thread编程时要先创立线程但是今天我们先不创建,试试看直接用main函数能不能让跑马灯亮起来。

我们先从main函数的第一行看起:

rt_pin_mode(16,PIN_MODE_OUTPUT)这行的意思就是先确定我们要对哪个引脚进行操作然后确定它的输入输出模式,这里要解释一下括号中16是什么意思,首先让我们打开内核文件drv_gpio.c

static const struct pin_index pins[] = 
{
#if defined(GPIOA)
    __STM32_PIN(0 ,  A, 0 ),
    __STM32_PIN(1 ,  A, 1 ),
    __STM32_PIN(2 ,  A, 2 ),
    __STM32_PIN(3 ,  A, 3 ),
    __STM32_PIN(4 ,  A, 4 ),
    __STM32_PIN(5 ,  A, 5 ),
    __STM32_PIN(6 ,  A, 6 ),
    __STM32_PIN(7 ,  A, 7 ),
    __STM32_PIN(8 ,  A, 8 ),
    __STM32_PIN(9 ,  A, 9 ),
    __STM32_PIN(10,  A, 10),
    __STM32_PIN(11,  A, 11),
    __STM32_PIN(12,  A, 12),
    __STM32_PIN(13,  A, 13),
    __STM32_PIN(14,  A, 14),
    __STM32_PIN(15,  A, 15),
#if defined(GPIOB)
    __STM32_PIN(16,  B, 0),
    __STM32_PIN(17,  B, 1),
    __STM32_PIN(18,  B, 2),
    __STM32_PIN(19,  B, 3),
    __STM32_PIN(20,  B, 4),
    __STM32_PIN(21,  B, 5),
    __STM32_PIN(22,  B, 6),
    __STM32_PIN(23,  B, 7),
    __STM32_PIN(24,  B, 8),
    __STM32_PIN(25,  B, 9),
    __STM32_PIN(26,  B, 10),
    __STM32_PIN(27,  B, 11),
    __STM32_PIN(28,  B, 12),
    __STM32_PIN(29,  B, 13),
    __STM32_PIN(30,  B, 14),
    __STM32_PIN(31,  B, 15),
#if defined(GPIOC)
    __STM32_PIN(32,  C, 0),
    __STM32_PIN(33,  C, 1),
    __STM32_PIN(34,  C, 2),
    __STM32_PIN(35,  C, 3),
    __STM32_PIN(36,  C, 4),
    __STM32_PIN(37,  C, 5),
    __STM32_PIN(38,  C, 6),
    __STM32_PIN(39,  C, 7),
    __STM32_PIN(40,  C, 8),
    __STM32_PIN(41,  C, 9),
    __STM32_PIN(42,  C, 10),
    __STM32_PIN(43,  C, 11),
    __STM32_PIN(44,  C, 12),
    __STM32_PIN(45,  C, 13),
    __STM32_PIN(46,  C, 14),
    __STM32_PIN(47,  C, 15),
#if defined(GPIOD)
    __STM32_PIN(48,  D, 0),
    __STM32_PIN(49,  D, 1),
    __STM32_PIN(50,  D, 2),
    __STM32_PIN(51,  D, 3),
    __STM32_PIN(52,  D, 4),
    __STM32_PIN(53,  D, 5),
    __STM32_PIN(54,  D, 6),
    __STM32_PIN(55,  D, 7),
    __STM32_PIN(56,  D, 8),
    __STM32_PIN(57,  D, 9),
    __STM32_PIN(58,  D, 10),
    __STM32_PIN(59,  D, 11),
    __STM32_PIN(60,  D, 12),
    __STM32_PIN(61,  D, 13),
    __STM32_PIN(62,  D, 14),
    __STM32_PIN(63,  D, 15),
#if defined(GPIOE)
    __STM32_PIN(64,  E, 0),
    __STM32_PIN(65,  E, 1),
    __STM32_PIN(66,  E, 2),
    __STM32_PIN(67,  E, 3),
    __STM32_PIN(68,  E, 4),
    __STM32_PIN(69,  E, 5),
    __STM32_PIN(70,  E, 6),
    __STM32_PIN(71,  E, 7),
    __STM32_PIN(72,  E, 8),
    __STM32_PIN(73,  E, 9),
    __STM32_PIN(74,  E, 10),
    __STM32_PIN(75,  E, 11),
    __STM32_PIN(76,  E, 12),
    __STM32_PIN(77,  E, 13),
    __STM32_PIN(78,  E, 14),
    __STM32_PIN(79,  E, 15),
#if defined(GPIOF)
    __STM32_PIN(80,  F, 0),
    __STM32_PIN(81,  F, 1),
    __STM32_PIN(82,  F, 2),
    __STM32_PIN(83,  F, 3),
    __STM32_PIN(84,  F, 4),
    __STM32_PIN(85,  F, 5),
    __STM32_PIN(86,  F, 6),
    __STM32_PIN(87,  F, 7),
    __STM32_PIN(88,  F, 8),
    __STM32_PIN(89,  F, 9),
    __STM32_PIN(90,  F, 10),
    __STM32_PIN(91,  F, 11),
    __STM32_PIN(92,  F, 12),
    __STM32_PIN(93,  F, 13),
    __STM32_PIN(94,  F, 14),
    __STM32_PIN(95,  F, 15),
#if defined(GPIOG)
    __STM32_PIN(96,  G, 0),
    __STM32_PIN(97,  G, 1),
    __STM32_PIN(98,  G, 2),
    __STM32_PIN(99,  G, 3),
    __STM32_PIN(100, G, 4),
    __STM32_PIN(101, G, 5),
    __STM32_PIN(102, G, 6),
    __STM32_PIN(103, G, 7),
    __STM32_PIN(104, G, 8),
    __STM32_PIN(105, G, 9),
    __STM32_PIN(106, G, 10),
    __STM32_PIN(107, G, 11),
    __STM32_PIN(108, G, 12),
    __STM32_PIN(109, G, 13),
    __STM32_PIN(110, G, 14),
    __STM32_PIN(111, G, 15),
#if defined(GPIOH)
    __STM32_PIN(112, H, 0),
    __STM32_PIN(113, H, 1),
    __STM32_PIN(114, H, 2),
    __STM32_PIN(115, H, 3),
    __STM32_PIN(116, H, 4),
    __STM32_PIN(117, H, 5),
    __STM32_PIN(118, H, 6),
    __STM32_PIN(119, H, 7),
    __STM32_PIN(120, H, 8),
    __STM32_PIN(121, H, 9),
    __STM32_PIN(122, H, 10),
    __STM32_PIN(123, H, 11),
    __STM32_PIN(124, H, 12),
    __STM32_PIN(125, H, 13),
    __STM32_PIN(126, H, 14),
    __STM32_PIN(127, H, 15),
#if defined(GPIOI)
    __STM32_PIN(128, I, 0),
    __STM32_PIN(129, I, 1),
    __STM32_PIN(130, I, 2),
    __STM32_PIN(131, I, 3),
    __STM32_PIN(132, I, 4),
    __STM32_PIN(133, I, 5),
    __STM32_PIN(134, I, 6),
    __STM32_PIN(135, I, 7),
    __STM32_PIN(136, I, 8),
    __STM32_PIN(137, I, 9),
    __STM32_PIN(138, I, 10),
    __STM32_PIN(139, I, 11),
    __STM32_PIN(140, I, 12),
    __STM32_PIN(141, I, 13),
    __STM32_PIN(142, I, 14),
    __STM32_PIN(143, I, 15),
#if defined(GPIOJ)
    __STM32_PIN(144, J, 0),
    __STM32_PIN(145, J, 1),
    __STM32_PIN(146, J, 2),
    __STM32_PIN(147, J, 3),
    __STM32_PIN(148, J, 4),
    __STM32_PIN(149, J, 5),
    __STM32_PIN(150, J, 6),
    __STM32_PIN(151, J, 7),
    __STM32_PIN(152, J, 8),
    __STM32_PIN(153, J, 9),
    __STM32_PIN(154, J, 10),
    __STM32_PIN(155, J, 11),
    __STM32_PIN(156, J, 12),
    __STM32_PIN(157, J, 13),
    __STM32_PIN(158, J, 14),
    __STM32_PIN(159, J, 15),
#if defined(GPIOK)
    __STM32_PIN(160, K, 0),
    __STM32_PIN(161, K, 1),
    __STM32_PIN(162, K, 2),
    __STM32_PIN(163, K, 3),
    __STM32_PIN(164, K, 4),
    __STM32_PIN(165, K, 5),
    __STM32_PIN(166, K, 6),
    __STM32_PIN(167, K, 7),
    __STM32_PIN(168, K, 8),
    __STM32_PIN(169, K, 9),
    __STM32_PIN(170, K, 10),
    __STM32_PIN(171, K, 11),
    __STM32_PIN(172, K, 12),
    __STM32_PIN(173, K, 13),
    __STM32_PIN(174, K, 14),
    __STM32_PIN(175, K, 15),

在这里我们为每一个引脚都定义了一个宏,在开发板里led的绿灯所对应的引脚是PB0也就是gpioB第0号引脚(具体可以看开发板的原理图)

__STM32_PIN(16, B, 0),

这一行就是为PB0定义的宏,可以看到在括号内有3个数字,第一个数字就是这个引脚所对应的编号也就是16。

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

int main(){
rt_pin_mode(16,PIN_MODE_OUTPUT);
  
  while(1){
    rt_pin_write(16,PIN_LOW);
    rt_thread_mdelay(500);
    rt_pin_write(16,PIN_HIGH);
    rt_thread_mdelay(500);
  }
}

再让我们回到main函数中,括号里第二个参数应该很好理解,就是选择推挽输出模式同样再内核文件中也能看到关于输入输出的宏。

#define PIN_MODE_OUTPUT         0x00
#define PIN_MODE_INPUT          0x01
#define PIN_MODE_INPUT_PULLUP   0x02
#define PIN_MODE_INPUT_PULLDOWN 0x03
#define PIN_MODE_OUTPUT_OD      0x04

那么这一行是什么意思就一目了然了,选择PB0这个引脚并使用推挽输出模式。接下来让我们看while循环,(1)表示此循环无限运作下去(但通过对线程的操控可以让它关闭,具体的以后会讲)

rt_pin_write(16,PIN_LOW); 
rt_thread_mdelay(500); 
rt_pin_write(16,PIN_HIGH);
rt_thread_mdelay(500);

while循环中是我们用来点亮跑马灯并且让他闪烁的,第一句就是让PB0输出低电平,在rt_thread中所有引用的参数都是事先定义好的宏所以可以参照上文来找到它的原型,第二句是时钟函数就是延时50ms后读取下一行代码,rt_thread中时钟函数有很多种,如rt_thread_delay(使用系统滴答来完成延时操作,括号内参数应改成50,也就是50个系统滴答)。在搞清楚前两行代码是什么意思后整个while循环的意思就应该很清晰了,通过对引脚输出高低电平并适当的延时就能完成闪烁这个操作。

在rt_thread中用main函数来实现跑马灯的功能还是算蛮简单的,如果有学习过库函数编程应该会一看就懂,但是这种做法在现实中是几乎不会使用的,因为这样子编程和使用库函数编程没什么区别,也就是无法体现出tr_thread的特点。正常情况下是要创建线程后再进行用户函数的编写。

 

通过ESP8266模块与JQ8900模块与RTT实现手机远程控制音乐播放

本例程是一个简单的串口通信的交互例程,通过了RT-thread系统来实现。

简要叙述程序思路:
  1. 创建两个进程句柄,serial与serial2。
  2. 调用一系列初始化串口设备的函数,分别是rt_device_find(查找设备),rt_device_set_rx_indicate(设置接收回调函数)。
  3. 启动线程,调用函数并检测接受到的信息
  4. 接收到对应信息后调用函数向JQ8900模块发送命令
  5. 同时保留了按钮操控的效果,通过rt_pin_attach_irq设置按钮中断函数

以下给出源码:

/*
基于RTT的手机通过esp8266操作JQ8900功放模块的一种方式
所用开发板 野火指南者
WiFi模块   ESP8266
功放模块   JQ8900
RTT版本    4.0.2(野火BSP)
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#ifndef KEY0_PIN_NUM
    #define KEY0_PIN_NUM      GET_PIN(A, 0) /* PA0 Key1 pin脚*/
#endif
#ifndef KEY1_PIN_NUM
    #define KEY1_PIN_NUM      GET_PIN(C, 13) /* PC13 Key2 pin脚*/
#endif
#ifndef SAMPLE_UART_NAME
#define SAMPLE_UART_NAME       "uart2"        /* JQ8900所接串口 请先事先在env中打开*/
#endif
#ifndef UART_NAME
#define UART_NAME              "uart3"        /* ESP8266所接串口*/
#endif
static rt_device_t serial;   
static rt_device_t serial2;   
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 句柄默认设置 */
static struct rt_semaphore rx_sem;
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    rt_sem_release(&rx_sem);
    return RT_EOK;
}
static void Re_music(void *args)
{
    static char buffer1[4]={0xAA,0x05,0x00,0xAF};    //JQ8900上一曲命令
    rt_uint32_t tx_length;
    tx_length=4;
    int i = 0;
    while(i < 4){
    rt_device_write(serial,0,&buffer1[i],tx_length);  //循环发送
    i++;}
    rt_kprintf("jun\n");
}
static void next_music(void *args)
{
    static char buffer1[4]={0xAA,0x06,0x00,0xB0};    //JQ8900下一曲命令
    rt_uint32_t tx_length;
    tx_length=4;
    int i = 0;
    while(i < 4){
    rt_device_write(serial,0,&buffer1[i],tx_length);
    i++;}
    rt_kprintf("bian\n");
}

static void nmsl(void *parameter){
  
      char ch;

    while (1)
    {
        while (rt_device_read(serial2, -1, &ch, 1) != 1)
        {
            rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
        }
        if(ch == 'a'){         //esp8266检测接受信息,为a则调用函数
          Re_music(0);
        }
        if(ch == 'b'){
          next_music(0);
        }

}
}

static void serial_thread_entry(void *parameter)
{
  //  char ch;
    while (1)
      
    {    
    rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, Re_music, RT_NULL);   //设置按钮中断回调函数
    rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
    rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, next_music, RT_NULL);
    rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);    
    }
}
static int mdr5(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];
    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    }
    serial = rt_device_find(uart_name);
    serial2 = rt_device_find(UART_NAME);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }
    if (!serial2)
    {
        rt_kprintf("find %s failed!\n", UART_NAME);
        return RT_ERROR;
    }
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
    config.baud_rate = BAUD_RATE_9600;    //串口设置为9600
    config.data_bits = DATA_BITS_8;
    config.stop_bits = STOP_BITS_1;
    config.parity = PARITY_NONE;
    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    rt_device_set_rx_indicate(serial, uart_input);
    
    rt_device_open(serial2, RT_DEVICE_FLAG_INT_RX);
    rt_device_set_rx_indicate(serial2, uart_input);
    rt_thread_t thread1 = rt_thread_create("serial2", nmsl, RT_NULL, 1024, 25, 10);
        if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }
    else
    {
        ret = RT_ERROR;
    }
    
    
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }
    return ret;
}
MSH_CMD_EXPORT(mdr5, uart JUN);

值得注意的是,JQ8900模块的波特率是9600,需要使用rt_device_control函数来设置串口波特率,而esp8266则不用

本程序还设置了一个接受信号量,用来在串口接收到信息之后发送信号量,来表示已经接受到数据。

如何通过RTT开启野火开发板上的sdcard,并且在msh中读取

野火开发板上有一个sdcard接口,通过RTT,我们可以非常方便的添加开发板相关的驱动,并且在RT-thread的Final sh中使用并读取sd卡的内容。

在开始本教程之前请先确保您已经安装RT-thread的ENV工具包。

首先,我们移动到RTT的bsp目录下,右键空白处,选择ConEmu Here

在Env控制台中输入menuconfig

进入menuconfig界面后进入到Hardware Drivers Config–>Onboard Peripheral Drivers

打开Enable SDCARD (sdio)

保存后在Env命令台中输入scons –target=mdk5

运行后,重新打开项目文件,即可发现项目中的Filesystem Group

重新烧录工程后,即可操作RTT上的文件操作系统

 \ | /
- RT -     Thread Operating System
 / | \     4.0.2 build Aug 22 2019
 2006 - 2019 Copyright by rt-thread team
msh />[I/SDIO] SD card capacity 15273984 KB.
found part[0], begin: 4194304, size: 14.576GB
[I/app.card] sd card mount to '/'
[D/at.dev] 

msh />ls
Directory /:
pic1.bmp            460854                   
pic2.bmp            115254                   
pic3.bmp            460854                   
pic4.bmp            460854                   
pic5.bmp            50678                    
pic6.bmp            44802

如上串口信息所示,可以调用sd卡中的文件

JQ8900的RTT[RT-thread]简易测试代码

-以下为示例代码

可以通过开发板上的按钮来对JQ8900进行操作,达到切换曲目的效果

并且可以自定义修改代码进行更多操作,优化了串口发送信息的方式。

/*
通过RTT例子修改
用于测试YSV0.7是否可用
*/

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>


#define LED0_PIN    GET_PIN(B, 1)    //定位到野火开发板上两个RGB相关的寄存器上
#define LED0_PINE   GET_PIN(B, 0)
#ifndef KEY0_PIN_NUM
    #define KEY0_PIN_NUM      GET_PIN(A, 0) /* PA0 */
#endif
#ifndef KEY1_PIN_NUM
    #define KEY1_PIN_NUM      GET_PIN(C, 13) /* PC13 */
#endif
#define SAMPLE_UART_NAME       "uart2"

static rt_device_t serial;              /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置参数 */


/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;




/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    rt_sem_release(&rx_sem);

    return RT_EOK;
}

static void Re_music(void *args)
{
    static char buffer1[4]={0xAA,0x05,0x00,0xAF};
    rt_uint32_t tx_length;
    tx_length=4;
    int i = 0;
    while(i < 4){
    rt_device_write(serial,0,&buffer1[i],tx_length);
    i++;}
    rt_kprintf("上一条\n");
}
static void next_music(void *args)
{
    static char buffer1[4]={0xAA,0x06,0x00,0xB0};
    rt_uint32_t tx_length;
    tx_length=4;
    int i = 0;
    while(i < 4){
    rt_device_write(serial,0,&buffer1[i],tx_length);
    i++;}
    rt_kprintf("下一条\n");

}

static void serial_thread_entry(void *parameter)
{

  //  char ch;
    int jun = 1;
    while (1)
      
    {		
             /*发送缓冲区是固定为5个字节长度的*/
        static char uart2_tx_buffer[5]={0xAA,0x02,0x00,0xAC,0x05};
        rt_uint32_t tx_length;
        tx_length=5;
    rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, Re_music, RT_NULL);
    rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);


    rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, next_music, RT_NULL);
    rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
        if(jun){
        rt_device_write(serial,0,&uart2_tx_buffer[0],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[1],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[2],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[3],tx_length);
        rt_kprintf("测试成功!");}
        /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */					
             jun = 0;
    }
}

static int mdr1(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];

    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    }

    /* 查找系统中的串口设备 */
    serial = rt_device_find(uart_name);

    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }

    /* 初始化信号量 */
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
    config.baud_rate = BAUD_RATE_9600;    //配置串口通信为9600
    config.data_bits = DATA_BITS_8;
    config.stop_bits = STOP_BITS_1;
    config.parity = PARITY_NONE;
    /* 打开设备后才可修改串口配置参数 */
    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_input);
    /* 发送字符串 */
//    rt_device_write(serial, 0, str, (sizeof(str) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }

    return ret;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mdr1, uart JUN);

可以自行添加到RT-thread的BSP中,并烧录到开发板中。

JQ8900模块与单片机通信,达到功放效果|基于RTT

上一篇我们已经进行了JQ8900模块的串口调试,这次我们来把它接上单片机来进行调试。

实现了RTT的串口传输16进制数据

-将JQ8900模块上的串口相关线接上开发板

注意串口TX与RX的反接,笔者这里接了开发板上的UART2接口,分别是PA2(TX),PA3(RX)。

接下来进行程序的编写,我们根据JQ8900的串口手册,相关定义如下

我们进行最简单的播放测试,即01条

以下是测试代码:

/*
通过RTT例子修改
用于测试JQ8900是否可用
*/

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>


#define SAMPLE_UART_NAME       "uart2"

static rt_device_t serial;              /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置参数 */


/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;

/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    rt_sem_release(&rx_sem);

    return RT_EOK;
}




static void serial_thread_entry(void *parameter)
{
  //  char ch;
    int jun = 1;
    while (1)
      
    {		
             /*发送缓冲区是固定为5个字节长度的*/
        static char uart2_tx_buffer[5]={0xAA,0x02,0x00,0xAC,0x05};//测试字段,作用为播放当前音乐
        rt_uint32_t tx_length;
        tx_length=5;
        if(jun){
        rt_device_write(serial,0,&uart2_tx_buffer[0],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[1],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[2],tx_length);
        rt_device_write(serial,0,&uart2_tx_buffer[3],tx_length);
        rt_kprintf("device message sent!");}
        /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */						
             jun = 0;
    }
}

static int mdr1(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];

    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    }

    /* 查找系统中的串口设备 */
    serial = rt_device_find(uart_name);

    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }

    /* 初始化信号量 */
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
    config.baud_rate = BAUD_RATE_9600;    //配置串口通信为9600
    config.data_bits = DATA_BITS_8;
    config.stop_bits = STOP_BITS_1;
    config.parity = PARITY_NONE;
    /* 打开设备后才可修改串口配置参数 */
    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_input);

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }

    return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mdr1, uart JUN);

 

代码简要解析

  1. 打开并配置了串口通信为9600,数据位为8,停止位为1
  2. 设置的回调函数中使用了一个数组用来储存16进制,HEX数据
  3. 使用rt_device_write函数分4次把一个完整的命令传输到JQ8900
  4. 实现了串口通信

将本页代码添加到您的RTT-BSP中,编译后进行烧录,通过msh发送mdr1

 \ | /
- RT -     Thread Operating System
 / | \     4.0.2 build Aug 22 2019
 2006 - 2019 Copyright by rt-thread team
msh />mdr1
msh />device message sent!

 

即可听到JQ8900喇叭的声音,测试成功

 

通过串口方式给ESP8266烧写固件

其中ESP8266的VCC脚为3V3,CH_PD脚为EN脚,GPIO0为IO0脚。

首先按照这个方式将esp8266与开发板连接起来(需要先拔掉串口1和串口3上的总共4个跳帽)

具体连接方式参考

其中3V3口与EN口可接3V3,也可不接

由于野火开发板的esp8266的flash只有512K的大小,所以烧不了安信可的最新固件,只能烧勉强不算太旧的固件,这里给出安信可的V0.9.5.6固件

下载ESP8266一键下载工具       提取码:7z71

将开发板上电后

打开esp8266一键下载工具,切到配置页面

只保留//Flash前的小叉,去掉另外3个小叉,之后再点击//Flash旁的小齿轮,选择到刚刚下载的固件

确认线没有接错之后,切换到操作界面,选择CH340所对应的端口号,点击一键烧写

没有错误的话,将会开始烧写过程,等待进度条跑完,左下角会显示绿色的勾

由于笔者将会使用RTT的AT组件进行调试,如果不使用RTT的话,可以直接打开SScom进行ESP8266的调试工作

输入AT+GMR将会显示固件信息

断电,拔掉所有外接杜邦线,并接回跳帽,重新上电,观察串口传回信息,并在At client中验证版本信息

 \ | /
- RT -     Thread Operating System
 / | \     4.0.2 build Aug 18 2019
 2006 - 2019 Copyright by rt-thread team
[I/sal.skt] Socket Abstraction Layer initialize success.
[I/at.clnt] AT client(V1.3.0) on device uart3 initialize success.
[D/at.dev] the network interface device(esp0) set up status
[D/at.dev] esp8266 device(esp0) initialize start.
msh />[I/SDIO] SD card capacity 15273984 KB.
found part[0], begin: 4194304, size: 14.576GB
[E/at.clnt] Read response buffer failed. The Response buffer size is out of buffer size(512)!
[I/app.card] sd card mount to '/'
[D/at.dev] AT version:0.21.0.0
[D/at.dev] SDK version:0.9.5
[D/at.dev] 
[I/at.dev] esp8266 device(esp0) network initialize successfully.
[E/at.dev] esp8266 device(esp0) prase "AT+CIPSTA?" command resposne data error.
at client
======== Welcome to using RT-Thread AT command client cli ========
Cli will forward your command to server port(uart3). Press 'ESC' to exit.
AT+GMR
AT version:0.21.0.0
SDK version:0.9.5

OK

ESP8266烧写固件结束