单片机怎么驱动wifi_spi接口的wifi模组单片机驱动 - CSDN
  • 51单片机连接ESP8266串口WiFi模块

    万次阅读 多人点赞 2019-08-28 15:39:58
    51单片机 VCC 3.3V (5V连接时间过长可能会烧坏WiFi模块) GND GND CH_PD 3.3V GPIO0 GND UTXD TXD URXD RXD 烧录固件 打开烧录软件,选择配置选项卡,点击第二栏的小齿轮图标 - 加载固件...

    烧录固件

    引脚连接

    WiFi 51单片机
    VCC 3.3V (5V连接时间过长可能会烧坏WiFi模块)
    GND GND
    CH_PD 3.3V
    GPIO0 GND
    UTXD TXD
    URXD RXD

    烧录固件

    1. 打开烧录软件,选择配置选项卡,点击第二栏的小齿轮图标 - 加载固件地址
      配置选项卡
    2. 点击其他选项的选择栏,取消选中,只选择第二个选项(固件地址)
      配置固件位置
    3. 返回操作选项卡,选择正确COM端口,点击一键烧写
    4. 模块重新上电,下载开始,等待下载完成
      成功样图

    串口助手测试ESP8266串口WiFi模块

    引脚连接

    WiFi 51单片机
    VCC 3.3V (5V连接时间过长可能会烧坏WiFi模块)
    GND GND
    CH_PD 3.3V
    UTXD TXD
    URXD RXD

    注:此时连接状态 WiFi模块–>51单片机–>串口助手(PC),由于WiFi模块和单片机的TXD和RXD处于正接状态,单片机和WiFi模块并不通信,相当于WiFi模块直连串口助手。

    AT常用指令

    ESP8266串口WiFi模块分为三种工作模式:Station模式(类似无线终端),AP模式(提供无线接入服务),AP模式兼Station模式。

    通过串口助手测试WiFi模块时,

    测试

    发送指令:AT

    响应:OK

    重启模块

    发送命令:AT+RST

    响应 :OK

    设置模块

    发送指令:AT+CWMODE = <mode>

    说明:需重启后生效(AT+RST)

    <mode>:1-Station模式,2-AP模式,3-AP兼Station模式。
    

    响应:OK

    配置AP参数

    发送命令:

    指令:AT+ CWSAP= <ssid>,<pwd>,<chl>, <ecn>

    说明:指令只有在AP模式开启后有效

    <ssid>:字符串参数,接入点名称 
    <pwd>:字符串参数,密码最长64字节,ASCII
    <chl>:通道号 
    <ecn>:加密模式,0-OPEN,1-WEP,2-WPA_PSK,3-WPA2_PSK,4-WPA_WPA2_PSK  
    

    示例:AT+CWSAP=”TEST”,”123456123456”,1,3

    响应:OK

    开启多连接模式

    发送命令:AT+CIPMUX=<mode>

    说明:<mode> :0-单路连接模式,1-多路连接模式

    响应:OK

    创建服务器

    发送命令:AT+CIPSERVER=<mode>,<port>

    说明:AT+ CIPMUX=1时才能开启服务器;关闭server模式需要重启。开启server后自动建立server监听,当有client接入会自动按顺序占用一个连接。

    <mode>:0-关闭server模式,1-开启server模式       
    <port>:端口号,缺省值为333 
    

    响应:OK

    通过51单片机初始化WiFi模块

    通过串口助手测试AT指令可以发现,部分AT指令断电不会保存,因此需要在代码中进行初始化设置。

    引脚连接

    WiFi 51单片机
    VCC 3.3V (5V连接时间过长可能会烧坏WiFi模块)
    GND GND
    CH_PD 3.3V
    UTXD RXD
    URXD TXD

    注:此时连接状态 WiFi模块–>51单片机–>串口助手(PC),由于WiFi模块和单片机的TXD和RXD处于反接状态,单片机和WiFi模块可以进行串口通信。

    实现代码

    #include<reg52.h>				//51单片机常用的头文件
    #define uchar unsigned char		//宏定义一个无符号的char类型
    #define uint unsigned int		//宏定义一个无符号的int类型
    
    //发送一字节
    void sendByte(uchar b)
    {
      SBUF = b;
      while(!TI);
      TI=0;
    }
    
    //发送字符串
    void sendString(uchar *s)
    {
      while(*s != '\0')	//字符串默认结尾'\0',以此来判断字符串的结束
      {
        sendByte(*s);
        s++;
      }
    }
    
    //初始化ESP8266WiFi模块
    void initEsp()
    {
      uint a;
      SCON = 0x50;    //8位数据,可变波特率
      TMOD = 0x20;    //设置定时器1位16位自动重装模式
      TL1 = 0xfd;     //设置定时器初值,波特率为9600
      TH1 = 0xfd;
      ET1 = 0;        //禁止定时器1中断
      TR1 = 1;        //启动定时器1
      EA = 1;
    
      for (a=0; a<50000; a++);		//延时几秒,让模块有时间启动
      sendString("AT+CWMODE=2\n");	//设置为softAP和station共存模式
    
      //WiFi热点不能通过代码设置,可使用串口助手进行设置,该设置断电不丢失
      /*
      for (a=0; a<20000; a++);
      sendString("AT+CWSAP='TEST','12345678',1,3\n"); //建立WiFi热点
      */
    
      for (a=0; a<50000; a++);    
      sendString("AT+CIPMUX=1\n"); //启动多连接
    
      for (a=0; a<20000; a++);
      sendString("AT+CIPSERVER=1,333\n");//建立server,端口为333
    
      for (a=0; a<20000; a++);
      sendString("AT+CIPSTO=50\n");     //服务器超时时间设置
    
      RI=0;
      ES=1;   //初始化完成,串行口中断打开
    }
    
    //主函数  
    void  main()  
    {  
      initEsp();
    }      
    

    注:代码中波特率应和串口助手的波特率相符,且部分AT命令如重启模块,设置WiFi热点等指令无法使用。

    获取WiFi传输的数据

    ESP8266WiFi模块作为TCP服务器接受来自客户端的信息前默认会加上+IPD,n,<string.length>:这样的字符,处理时应注意。

    实现代码

    //获取数据,数据格式示例:+IPD,0,14:"time":"11:11"
    void getData()
    {
      uint a;
      if(receiveFlag)
      {
        for(i=0; i<2; i++)
        {
          Hour[i]=Buffer[17+i];
        }
        Hour[2]='\0';
    
        for(i=0; i<2; i++)
        {
          Minute[i]=Buffer[20+i];
        }
        Minute[2]='\0';
    
        //将获取到的数据发送到串口助手上显示
        for (a=0; a<10000; a++);	//需要延时,负责会造成数据错乱导致丢包
        sendString(Hour);
        for (a=0; a<10000; a++);
        sendString(Minute);
    
        receiveFlag=0;
        count=0;
        for(i=0; i<22; i++)
        {
          Buffer[i]=0;
        }
      }
    }
    
    //主函数
    void main()
    {
      initEsp();			//初始化WiFi模块
      receiveFlag = 0;		//receiveFlag判断执行getData() 的标志
      count = 0;			//count缓冲区RXDdata[count]的索引  
      while(1)
      { 
        getData();
      }
    }
    
    //利用中断接收信息,且舍弃无效信息
    void uart() interrupt 4  
    {
      if(RI == 1)	
      {
        ES = 0;     //关闭串行中断
        RI = 0;     //清除串口接收标志位
        temp = SBUF; //从串口缓冲区取得数据
    
        if(count<20)	//满足需接收的信息长度,将数据存入缓冲区
        { 
          Buffer[count]=temp;
          count++;
          if(Buffer[0]=='+')  //判断是否为无效数据,由于WiFi模块会自动加上"+PID.."开头的字符串
          {
            receiveFlag = 1; 
          }
          else
          {
            receiveFlag = 0;
            count = 0;
          }
        } 
        ES = 1; 
      }
    }
    
    展开全文
  • 本文以STM32F1系列的单片机为例,详细讲解Marvell公司的88W8686/88W8782/88W8801 WiFi模块驱动程序的编写。编写程序时为了代码简短起见,直接用寄存器操作,不使用STM32库函数。IDE采用Keil uVision5。为了存储下...
    
    

    本文以STM32F1系列的单片机为例,详细讲解Marvell公司的88W8686/88W8782/88W8801 WiFi模块驱动程序的编写。编写程序时为了代码简短起见,直接用寄存器操作,不使用STM32库函数。IDE采用Keil uVision5。为了存储下WiFi模块庞大的固件,以及方便lwip的移植,请尽量采用较大SRAM和Flash容量的单片机(如High-density或XL-density系列的),这里笔者用的是STMF103RET6(引脚数64,SRAM容量64KB,Flash容量512KB)。因为Connectivity line系列的STM32F105/107单片机没有SDIO接口,所以请不要使用这两种单片机来测试。STM32F103C8T6容量太小,虽然Flash程序存储空间有64KB,但SRAM运行内存只有20KB,不方便lwip协议栈的移植,所以最好也不要使用(当然这种情况改用uip协议栈也行,uip在SRAM容量才1KB的ATMega16单片机上都能运行)。

    88W8686已经是比较老的芯片了,其数据手册(datasheet)的发布时间是2007年2月20日。淘宝网上可以买到芯片组(Chip Set)为88W8686的WM-G-MR-09模块,价格比较贵,85元一个。其固件(firmware)及Fedora Linux下的驱动程序可直接在Marvell的官方网站上下载到,压缩包名称为SD-8686-LINUX26-SYSKT-9.70.3.p24-26409.P45-GPL.zip,里面有两个固件:helper_sd.bin和sd8686.bin,最后修改日期都是2008年2月29日。

    卖家世讯电子提供了STM32103RET6驱动该网卡的程序。但该程序可靠性很差,代码既乱又复杂而且很难看懂,扫描热点时经常出现problem fetching packet from firmware, rewhile的错误,连接热点时有时候会出现认证失败的错误type=0x888e!,一连接失败就直接重启单片机,而且与WPA2-PSK认证有关的代码被封装到了wap_wpa2_lib.lib文件中,不开放源代码。这也是笔者写本教程的原因:自己编写出高可靠性的驱动程序!

    较新的88W8782和88W8801模块价格更便宜,大概20~30块钱一个,支持创建AP模式的热点,安卓手机可以不打补丁直接连接,但很多寄存器的地址和88W8686不一样。其固件和Linuxl驱动程序无法在Marvell的官方网站上下载到,但可在淘宝的卖家那里获得,还可以获得数据手册PDF文档。数据手册的发布时间分别为2011年4月6日和2013年8月19日。压缩包的名称分别为SD-UAPSTA-8782-FC13-MMC-14.69.12.p35-M2614336_B0-GPL_new.zip和SD-UAPSTA-8801-FC18-MMC-14.76.36.p61-C3X14090_B0-GPL.zip。每个模块只有一个固件,分别是sd8782_uapsta.bin和sd8801_uapsta.bin,最后修改日期分别为2012年8月16日和2015年2月25日。

     

    本文主要讲解88W8686模块,同时也会顺带说明如何在88W8782/8801这两款芯片上运行所写的程序。

    这些WiFi模块都是SD I/O卡,而我们平常在手机里面插的内存卡属于SD memory卡。SDIO card和SD memory card都可以用STM32单片机的SDIO接口来操作,但它们所支持的命令不一样。后者支持很多命令,比如复位命令CMD0、写数据块命令CMD24~25、读数据块命令CMD17~18等等。但前者就只支持CMD0、3、5、7、15、52、53这几个命令,并且CMD0不是复位命令,而是从SDIO模式切换到SPI模式的命令。并且,两者的初始化时序也不一样。

    在SD卡的官方网站https://www.sdcard.org/上可以下载到SD memory卡和SDIO卡的文档。

    其中,Part1_Physical_Layer_Simplified_Specification_Ver6.00是SD memory卡的文档,PartE1_SDIO_Simplified_Specification_Ver3.00是SDIO卡的文档。PartE7_Wireless_LAN_Simplified_Addendum_Ver1.10虽然是介绍SDIO WiFi卡的文档,但和本文所讲的WiFi模块没有任何关系,因此不必下载。


    接下来,笔者将参考Part E1文档,讲解SDIO WiFi卡的初始化方法。

    完整的代码请参考:http://blog.csdn.net/zlk1214/article/details/75410647(Marvell 88W8686 WiFi模块创建或连接热点,并使用lwip建立http服务器)

    工程的建立:在Keil中新建一个STM32F103RE的工程,并勾选上启动代码CMSIS/CORE和Device/Startup:

    如果要使用STM32的库函数,则还需要勾选Device/StdPeriph Drivers中的项目(本文不使用库函数,因此不必勾选):

    工程建好后,Keil已经帮我们自动添加好了启动文件startup_stm32f10x_hd.s,其中hd是指high-density。

    在其中创建main.c文件,以及存放WiFi驱动程序的文件WiFi.h和WiFi.c。


    以下是88W8686 WiFi模块与STM32单片机的连线。


    SDIO_CLK接PC12,SDIO_CMD接PD2,数据线D0~D3接PC8~11。

    在笔者所用的的开发板上,VCC3V3引脚不是直接连接到电源的,而是通过一个场效应管接到PB12上的。当开发板外接了5V的电源插头,并且PB12为低电平时,WiFi模块才通电工作。下载程序时,PB12输出高阻态,此时WiFi模块断电。每次单片机复位时,WiFi模块也就跟着自动复位。自己焊的板子可以用一个PNP三极管来代替场效应管Q1。



    【程序1】Marvell 88W8686 WiFi模块的初始化代码:http://blog.csdn.net/zlk1214/article/details/73693167

    [cpp] view plain copy
    1. RCC->AHBENR = RCC_AHBENR_SDIOEN;  
    2. RCC->APB1ENR = RCC_APB1ENR_TIM6EN;  
    3. RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN| RCC_APB2ENR_USART1EN;  
    4.   
    5. GPIOA->CRH = 0x000004b0;  
    6. GPIOB->CRH = 0x00030000; // PB12为WiFi模块电源开关, PB12=0时打开WiFi模块  
    7. GPIOC->CRH = 0x000bbbbb;  
    8. GPIOD->CRL = 0x00000b00;  
    9.   
    10. USART1->BRR = 0x271;  
    11. USART1->CR1 = USART_CR1_UE | USART_CR1_TE;  
    在main函数中,首先打开SDIO、GPIOA~D、定时器TIM6和串口USART1的时钟,需要先打开时钟才能使用这些STM32外设。这些外设分别在AHB、APB1和APB2总线上。AHB和APB2的时钟频率为72MHz,而APB1的频率为36MHz。STM32外接的外部高速晶振HSE只有8MHz,这些频率都是RCC上的PLL倍频器产生的。
    接下来配置PA~PD的I/O口。CRL负责Px0~Px7,CRH负责Px8~Px15。每个十六进制数位配置一个端口,最低位为Px8或Px0,最高位为Px15或Px7。

    对于USART1串口,发送端口USART1_TX为PA9,设置为复用推挽输出,速度为50MHz:b,接收端口USART1_RX为PA10,设置为浮空输入:4。

    PB12为外接的电源开关,最开始为高阻态,WiFi模块为断电状态。当设置CRH为3(推挽输出,速度为50MHz)后,因为GPIOB->ODR为0,所以PB12输出低电平,WiFi模块通电。

    PC8~11为SDIO数据端口D0~D3,PC12为SDIO时钟引脚,PD2为SDIO命令端口。这些都应该设置为复用推挽输出50MHz,因此都设为b。

    由STM32F103的参考手册(Reference manual)的9.1.11 GPIO configurations for device peripherals中的表格可知,所有的SDIO引脚都应该设为复用推挽输出。

    推挽和开漏输出的区别:推挽输出可以输出低电平(ODR=0)和高电平(ODR=1)。开漏输出可以输出低电平(ODR=0),但不能输出高电平,当ODR=1时输出高阻态,高阻态相当于断开了端口与电源的连接。

    GPIO_CRH/CRL配置方法(加粗的表示常用):

    每1位16进制数表示一个I/O端口。
    1为10MHz推挽输出(推挽输出适合直接驱动)(复用为9)
    2为2MHz推挽输出(复用为a)
    3为50MHz推挽输出(复用为b)

    5为10MHz开漏输出(开漏输出适合接三极管基极)(复用为d)
    6为2MHz开漏输出(复用为e)
    7为50MHz开漏输出(复用为f)

    0为模拟输入/输出
    4为浮空输入
    8为带上/下拉电阻的输入
    (ODR=0为下拉,1为上拉),上拉输入表示IDR的默认值为1,下拉输入表示IDR的默认值为0

    当使用带上下拉电阻的输入模式时,该端口对应的ODR位的值就表示默认的输入电平。当该端口悬空时,IDR=ODR。否则IDR就等于输入的电平值。

    一般情况下,PNP型三极管的发射极都是接的+5V,如果基极通过电阻接到单片机的I/O口上并配置为开漏输出,则当ODR=0时,三极管饱和导通,发射极与基极间的电压为0.7V,基极电阻两端的电压为4.3V;当ODR=1时输出高阻态,相当于基极直接悬空,三极管截止。所以开漏输出特别适合接三极管的基极。如果配置为推挽输出,那么当ODR=1时,I/O口输出的是3.3V的高电平,5V-3.3V=1.7V高于导通电压0.7V,因此三极管还是导通的,且基极电阻两端的电压为1V。

    所以,如果WiFi模块的电源上接的是PNP三极管,那么只有将PB12配置为开漏输出(7)后,才能通过ODR寄存器控制WiFi模块电源的通断。


    接着配置串口USART1,BRR为波特率。USART1是在APB2总线上的外设,该总线的时钟为72MHz,也就是72000000Hz。欲设置的波特率为115200,72000000 ÷ 115200 = 625 = 0x271,因此,BRR=0x271。CR1为控制寄存器,UE表示启动该外设,TE表示允许发送。

    为了在程序中使用printf函数向串口输出信息,需要引入stdio.h头文件,然后实现fputc函数。printf函数使用的是C语言标准输出流stdout,因此fp=stdout。ch为要输出的每个字符。若输出的是换行符\n,为了正确换行,需要先输出一个回车符\r组成\r\n。向USART1的DR寄存器写入数据前,必须先等待SR寄存器中的TXE位(发送缓冲区空)变为1。写入数据后,串口外设将自动发送数据。最后函数必须返回ch的原有内容。

    [cpp] view plain copy
    1. #include <stdio.h>  
    2.   
    3. int fputc(int ch, FILE *fp)  
    4. {  
    5.     if (fp == stdout)  
    6.     {  
    7.         if (ch == '\n')  
    8.         {  
    9.             while ((USART1->SR & USART_SR_TXE) == 0);  
    10.             USART1->DR = '\r';  
    11.         }  
    12.         while ((USART1->SR & USART_SR_TXE) == 0);  
    13.         USART1->DR = ch;  
    14.     }  
    15.     return ch;  
    16. }  

    此外还需在项目属性里勾选上“Use MicroLIB”选项。


    J-Link下载器配置:在Debug选项卡中选择J-Link作为调试工具,并在设置对话框里勾选上Reset and Run复选框,以便下载完成后程序能自动开始运行。



    STM32 SDIO外设的初始化

    串口初始化完毕后便开始执行Card_Init函数:SDIO卡初始化函数。

    [cpp] view plain copy
    1. printf("Initialization begins...\n");  
    2. SDIO->POWER = SDIO_POWER_PWRCTRL;  
    3. SDIO->CLKCR = SDIO_CLKCR_CLKEN | 178; // 初始化时最高允许的频率: 72MHz/(178+2)=400kHz  
    4. delay(5); // 延时可防止CMD5重发  
    首先将SDIO_POWER寄存器中的PWRCTRL位置位,启动SDIO外设。以下是STM32F1参考手册中的SDIO_POWER寄存器的说明:

    只有当该寄存器的第1~0位同时为1时才能启动该外设。

    在STM32F10x.h头文件中,有如下的定义:

    [cpp] view plain copy
    1. /******************  Bit definition for SDIO_POWER register  ******************/  
    2. #define  SDIO_POWER_PWRCTRL                  ((uint8_t)0x03)               /*!< PWRCTRL[1:0] bits (Power supply control bits) */  
    3. #define  SDIO_POWER_PWRCTRL_0                ((uint8_t)0x01)               /*!< Bit 0 */  
    4. #define  SDIO_POWER_PWRCTRL_1                ((uint8_t)0x02)               /*!< Bit 1 */  
    SDIO_POWER_PWRCTRL表示PWRCTRL的全部位,即第1~0位。SDIO_POWER_PWRCTRL_0表示第0位,SDIO_POWER_PWRCTRL_1表示第1位。


    接着配置SDIO_CLKCR寄存器。该寄存器的CLKEN位决定是否启用时钟引脚SDIO_CK,即是否向PC12引脚输出时钟信号。CLKDIV为时钟分频系数。

    PC12引脚上的频率为:SDIO外设的频率 ÷ (CLKDIV + 2)

    因为SDIO是AHB总线上的外设,所以SDIO外设的频率等于AHB总线的频率(记为HCLK),为72MHz。

    程序中配置的是CLKDIV=178,因此分频后,在PC12引脚上输出的时钟频率就是400kHz。这是SD卡在初始化时所允许的最高频率。只有当SDIO总线上挂接的所有SD卡都初始化完毕了之后,这一频率才允许提高。


    然后调用delay函数延时5毫秒。延时的目的是上电后使器件做好准备,降低CMD5命令重发的可能性,但这不能完全防止CMD5重发。delay函数的实现如下:

    [cpp] view plain copy
    1. // 延时n毫秒  
    2. void delay(uint16_t n)  
    3. {  
    4.     TIM6->ARR = 10 * n - 1; // nms  
    5.     TIM6->PSC = 7199; // 72MHz/7200=10kHz  
    6.     TIM6->CR1 = TIM_CR1_URS | TIM_CR1_OPM; // UG不置位UIF, 非循环模式  
    7.     TIM6->EGR = TIM_EGR_UG; // 保存设置  
    8.     TIM6->CR1 |= TIM_CR1_CEN; // 开始计时  
    9.     while ((TIM6->SR & TIM_SR_UIF) == 0); // 等待计时完毕  
    10.     TIM6->SR &= ~TIM_SR_UIF; // 清除溢出标志  
    11. }  
    该函数使用了STM32中的基本定时器TIM6。ARR为计数量,这里n=5,ARR=49,PSC为分频系数,PSC=7199为7200分频。这里要注意的是,虽然TIM6是APB1总线上的外设,但提供的时钟却是2×36MHz=72MHz(见下图),所以分频后是10kHz。OPM=1表示定时器溢出后自动关闭,即只计时一次。UG=1表示使ARR和PSC中的值立即生效。URS=1表示执行UG=1时不将溢出标志UIF置位。接着CEN置1开始计时,用while循环等待溢出标志UIF置位,起到延时的目的。当CNT的值从49跳变到0的瞬间,UIF置位,CEN自动清零关闭定时器,跳出while循环。然后清除UIF标志位,以便于下次延时。

    CEN刚置位时,CNT=0。经过1/(10kHz)=0.1ms后,CNT从0跳变到1。经过4.9ms后,CNT从48跳变到49。经过5ms后,CNT刚好从49跳变到0。



    SDIO卡的初始化流程见Part E1文档的图3-2。首先以空参数(ARG=0)发送一个CMD5命令,检查有无回应。若有回应,则设置参数ARG后再次发送CMD5,检查回应中的MP(Memory Present)位后决定之后的流程。

    在STM32 SDIO外设中,使用SDIO_CMD寄存器发送命令,使用SDIO_ARG寄存器设置命令参数。

    在SDIO_CMD寄存器中,CMDINDEX决定命令号,CPSMEN=1时发送命令(该位不会自动清零,只要写完寄存器后该位为1,就发送命令)。WAITRESP=00时不等待回应,WAITRESP=01时等待48位的短回应,WAITRESP=11时等待136位的的长回应。回应的内容保存在RESP1~4寄存器中。


    [cpp] view plain copy
    1. /* 发送CMD5: IO_SEND_OP_COND */  
    2. SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 5;  
    3. while (SDIO->STA & SDIO_STA_CMDACT);  
    4. Card_CheckCommand(); // 为了保险起见还是要检查一下是否要重发命令  
    5. if (SDIO->STA & SDIO_STA_CMDREND)  
    6. {  
    7.     SDIO->ICR = SDIO_ICR_CMDRENDC;  
    8.     Card_ShowShortResponse();  
    9. }  
    因为上电后ARG寄存器的默认值为0,所以这里没有写SDIO->ARG=0;。这里以空参数发送CMD5,只将WAITRESP的第0位置1,等待短回应。SDIO_STA_CMDACT=1表示命令正在发送。由于之前延时5ms并不能100%保证命令不会出现超时,所以调用Card_CheckCommand函数检查一下等待回应是否超时。如果超时就重发命令。若收到了回应,则SDIO_STA_CMDREND自动置1,对SDIO_ICR_CMDRENDC写1清除该位,然后调用Card_ShowShortResponse函数显示命令的回应内容。

    [cpp] view plain copy
    1. // 检查命令是否收到了回应, 若没收到则重发命令  
    2. void Card_CheckCommand(void)  
    3. {  
    4.     while (SDIO->STA & SDIO_STA_CTIMEOUT)  
    5.     {  
    6.         SDIO->ICR = SDIO_ICR_CTIMEOUTC; // 清除标志  
    7.         SDIO->CMD = SDIO->CMD; // 重发  
    8.         printf("Timeout! Resend CMD%d\n", SDIO->CMD & SDIO_CMD_CMDINDEX);  
    9.         while (SDIO->STA & SDIO_STA_CMDACT);  
    10.     }  
    11. }  
    命令重发检查:若经过了64个SDIO_CK时钟周期(64/400000s=0.16ms)后仍没有收到回应,SDIO_STA_CTIMEOUT位自动置1表明超时。此时执行循环体内的语句。先对SDIO_ICR_CTIMEOUTC写1清除该位,然后将CMD寄存器中的内容再次送入CMD寄存器,写完之后CPSMEN仍为1,ARG=0,自动重发CMD5命令,等待命令发送完毕后再次检查是否超时。若收到了回应,CTIMEOUT=0跳出循环。

    [cpp] view plain copy
    1. void Card_ShowShortResponse(void)  
    2. {  
    3.     printf("Command response received: CMD%d, RESP_%08x\n", SDIO->RESPCMD, SDIO->RESP1);  
    4. }  
    Card_ShowShortResponse函数用于显示短回应的内容,包括短回应的回应命令号和32位的回应内容。

    运行结果如下:

    [plain] view plain copy
    1. Initialization begins...  
    2. Command response received: CMD63, RESP_90ff8000  

    CMD5命令的回应格式是R4格式,长度为48位。其中第45~40位为回应命令号,保存在RESPCMD寄存器中,第39~8位为32位的回应内容,保存在RESP1寄存器中。

    如下图,从C开始到I/O OCR为RESP1,Reserved为RESPCMD(始终等于全1,也就是63)。

    因为RESP1=0x90ff8000,所以C=1,功能(Function)数为1,MP=0(是否有内存区域),S18A=0(是否已接受1.8V低电压),OCR寄存器的值为0xff8000。


    OCR寄存器的第23~15位为1,表明该SDIO卡支持2.7~3.6V的电压。

    值得注意的是,该程序假定SDIO总线上只接了WiFi模块,没有接其他任何东西,包括SD内存卡。所以不发送Part E1文档图3-2中的用于SD卡的CMD0复位命令和CMD8命令。

    因为CMD5的命令回应中,功能数NF=1>0,且OCR寄存器的值有效,所以需要再次发送CMD5,且这次参数ARG为主机设置的电压范围。因为我们不请求1.8V低电压模式,所以ARG中S18R=0。


    [cpp] view plain copy
    1. /* 设置参数VDD Voltage Window: 3.2~3.4V, 并再次发送CMD5 */  
    2. SDIO->ARG = 0x300000;  
    3. SDIO->CMD = SDIO->CMD;  
    4. while (SDIO->STA & SDIO_STA_CMDACT);  
    5. if (SDIO->STA & SDIO_STA_CMDREND)  
    6. {  
    7.     SDIO->ICR = SDIO_ICR_CMDRENDC;  
    8.     Card_ShowShortResponse();  
    9.     if (SDIO->RESP1 & _BV(31))  
    10.     {  
    11.         // Card is ready to operate after initialization  
    12.         printf("Number of I/O Functions: %d\n", (SDIO->RESP1 >> 28) & 7);  
    13.         printf("Memory Present: %d\n", (SDIO->RESP1 & _BV(27)) != 0);  
    14.     }  
    15. }  
    程序中的_BV(31)表示第31位,即检查RESP1寄存器中的第31位(C位)是否为1。
    [cpp] view plain copy
    1. #define _BV(n) (1u << (n))  
    C=1(图3-2中对应IO=1)表明卡已经准备好了。此时程序向串口输出NF和MP的值。

    [plain] view plain copy
    1. Command response received: CMD63, RESP_90300000  
    2. Number of I/O Functions: 1  
    3. Memory Present: 0  

    接下来,发送CMD3命令,获取WiFi模块的RCA相对地址,并保存到全局变量rca中。

    [cpp] view plain copy
    1. uint16_t rca;  
    2.   
    3. /* 获取Wi-Fi模块地址 (CMD3: SEND_RELATIVE_ADDR, Ask the card to publish a new relative address (RCA)) */  
    4. SDIO->ARG = 0;  
    5. SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 3;  
    6. while (SDIO->STA & SDIO_STA_CMDACT);  
    7. if (SDIO->STA & SDIO_STA_CMDREND)  
    8. {  
    9.     SDIO->ICR = SDIO_ICR_CMDRENDC;  
    10.     rca = SDIO->RESP1 >> 16;  
    11.     printf("Relative card address: 0x%04x\n", rca);  
    12. }  
    由SD内存卡的文档Part1可知,CMD3的参数为0:

    在SDIO卡中CMD3的回应格式为R6,其高16位为RCA相对卡地址:

    获得相对地址后,发送CMD7选中WiFi模块,其参数ARG的高16位为欲选中模块的RCA地址,其余位为0。

    [cpp] view plain copy
    1. /* 选中Wi-Fi模块 (CMD7: SELECT/DESELECT_CARD) */  
    2. SDIO->ARG = rca << 16;  
    3. SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 7;  
    4. while (SDIO->STA & SDIO_STA_CMDACT);  
    5. if (SDIO->STA & SDIO_STA_CMDREND)  
    6. {  
    7.     SDIO->ICR = SDIO_ICR_CMDRENDC;  
    8.     printf("Card selected! status=0x%08x\n", SDIO->RESP1);  
    9. }  
    程序输出:

    [plain] view plain copy
    1. Relative card address: 0x0001  
    2. Card selected! status=0x00001e00  

    到此,WiFi模块已初始化完毕,现在可提升SDIO总线的时钟频率。频率不要设得太高(如24MHz),否则即便是不用库函数,用寄存器操作,发送/接收数据时也很容易忙不过来导致Underrun/Overrun的错误,除非使用DMA。

    [cpp] view plain copy
    1. // 提高时钟频率  
    2. SDIO->CLKCR = (SDIO->CLKCR & ~SDIO_CLKCR_CLKDIV) | 70; // 72MHz/(70+2)=1MHz  
    3. SDIO->DTIMER = 1000000; // 当频率为1MHz时, 超时时间为1秒  
    DTIMER寄存器表示SDIO接口在数据端口上发送或接收数据时的最大超时时间。

    SDIO接口开机后的默认数据宽度为1,只使用D0(PC8)这一根数据线。为了同时使用D0~D3四根数据线,需要修改WiFi模块中的卡公共控制寄存器(CCCR寄存器,见Part E1文档的6.9节),将地址为0x07的总线接口控制寄存器(Bus Interface Control)中的Bus Width位改为10。

    [cpp] view plain copy
    1. /* 选择总线宽度 (Wide Bus Selection) */  
    2. // For an SDIO card a write to the CCCR using CMD52 is used to select bus width. (See 4.5 Bus Width)  
    3. // CMD52: IO_RW_DIRECT, CCCR: Card Common Control Registers  
    4. Card_Write(0, 0x07, Card_Read(0, 0x07) | 0x02); // Bus Width: 4-bit bus  
    5. SDIO->CLKCR |= SDIO_CLKCR_WIDBUS_0;  

    SDIO_CLKCR寄存器中的WIDBUS位控制的是SDIO外设的数据宽度,当WIDBUS=01时采用四位数据线模式。


    这里涉及到SDIO卡内寄存器的读取(Card_Read)和写入(Card_Write),这是通过发送CMD52命令实现的。

    [cpp] view plain copy
    1. void Card_SendCMD52(uint8_t is_write, uint8_t read_after_write, uint8_t function_number, uint32_t register_address, uint8_t data_to_write)  
    2. {  
    3.     SDIO->ARG = (is_write << 31) | (function_number << 28) | (read_after_write << 27) | (register_address << 9) | data_to_write;  
    4.     SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 52;  
    5.     while (SDIO->STA & SDIO_STA_CMDACT);  
    6. }  

    CMD52命令参数的格式如下图所示:


    其中,R/W flag决定是读寄存器还是写寄存器,Function Number为寄存器所在的功能区,RAW flag表示写寄存器后是否自动读取寄存器的实际内容,Stuff为始终为0的填充位,Register Address为寄存器地址,Write Data为要写入的数据,读寄存器时用0填充。

    [cpp] view plain copy
    1. // 读寄存器  
    2. uint8_t Card_Read(uint8_t func_num, uint32_t addr)  
    3. {  
    4.     Card_SendCMD52(0, 0, func_num, addr, 0);  
    5.     if (SDIO->STA & SDIO_STA_CMDREND)  
    6.     {  
    7.         SDIO->ICR = SDIO_ICR_CMDRENDC;  
    8.         return SDIO->RESP1 & 0xff;  
    9.     }  
    10.     else  
    11.     {  
    12.         printf("Card_Read failed, SDIO->STA=0x%08x!\n", SDIO->STA);  
    13.         return 0;  
    14.     }  
    15. }  
    16.   
    17. // 写寄存器, 返回写入后寄存器的实际内容  
    18. uint8_t Card_Write(uint8_t func_num, uint32_t addr, uint8_t value)  
    19. {  
    20.     Card_SendCMD52(1, 1, func_num, addr, value);  
    21.     if (SDIO->STA & SDIO_STA_CMDREND)  
    22.     {  
    23.         SDIO->ICR = SDIO_ICR_CMDRENDC;  
    24.         return SDIO->RESP1 & 0xff;  
    25.     }  
    26.     else  
    27.     {  
    28.         printf("Card_Write failed, SDIO->STA=0x%08x!\n", SDIO->STA);  
    29.         return 0;  
    30.     }  
    31. }  
    写寄存器时,RAW=1,自动返回写入后寄存器中的实际内容。

    CMD52命令的回应格式为R5,其中不仅包含了8位的寄存器内容,还包括卡状态信息Response Flags,所以RESP1必须要与上0xff滤掉卡状态信息位。


    CCCR寄存器和以后要讲的FBR寄存器和CIS卡信息结构,都位于0号功能区(Function 0)中的公共I/O区域(CIA)中。


    接下来我们来讨论一下,如果SDIO总线上同时接了WiFi模块和SD内存卡,该怎么初始化。

    笔者建议大家最好不要将WiFi模块和SD卡同时接到STM32的SDIO接口上,因为这样会产生很多兼容性问题。如果必须要同时使用WiFi模块和SD内存卡,例如要用WiFi做一个HTTP服务器浏览保存在SD卡上的网页,或是要实现FTP协议读写SD卡,则最好WiFi模块单独使用SDIO接口,SD卡使用SPI接口。


    SD内存卡的常规初始化顺序是:CMD0 -> CMD8 -> ACMD41 -> CMD2 -> CMD3 -> CMD7。

    当SDIO总线上直接并联多张SD内存卡时,CMD8、ACMD41、CMD2、CMD3都会产生碰撞,导致CRC校验错误(SDIO_STA_CCRCFAIL置位),甚至主机端完全无法接收命令,导致超时(SDIO_STA_CTIMEOUT置位)。更严重的是,用于分配RCA地址的CMD3命令无法屏蔽,若第一张SD卡已经分配好了RCA地址(RCA1),现在要再次发送CMD3命令给第二张SD卡分配RCA地址,但第一张卡仍会响应CMD3,最终第一张卡的RCA地址被更换,RCA1失效。为了解决这个问题,需要改变硬件电路。命令引脚SDIO_CMD和SDIO_D0~SDIO_D3直接连接在一起,但每个卡槽的时钟引脚CLKn则需要通过一个与门(74HC08)连接在一起,并在各与门的输入端增加一个片选I/O口CSn,其逻辑式为CSn & SDIO_CK = CLKn,其中SDIO_CK为单片机上的PC12引脚。初始化时,仅使一个CSn片选引脚有效(高电平),其他的卡槽都为无效(低电平),封锁住相应的时钟,这样就避免了碰撞。当所有的SD内存卡的RCA地址都已分配好,且各不相同时,再打开所有的CSn时钟,使用CMD7命令选择要操作的卡。


    【示例程序】STM32F407单片机SDIO接口上插入多张SD卡并进行通信:http://blog.csdn.net/zlk1214/article/details/76651382

    STM32F407单片机开机时默认的时钟为16MHz,而SDIO外设的时钟为48MHz,所以必须打开PLL倍频器后才能使用SDIO外设,否则将无法发送命令,程序卡死在SDIO_STA_CMDACT上。

    [cpp] view plain copy
    1. // 开机时默认的时钟: SYSCLK=HCLK=16MHz, PCLK1=PCLK2=16MHz    
    2. // SDIO的时钟是48MHz, 而系统时钟只有16MHz, 所以必须打开PLL倍频器    
    3. RCC->CR |= RCC_CR_PLLON;    
    4. while ((RCC->CR & RCC_CR_PLLRDY) == 0);    


    若SDIO总线上只有1张SD卡和1个WiFi模块,问题将变得简单很多。我们知道,SD I/O卡的初始化顺序是:

    CMD5 -> CMD5 -> CMD3 -> CMD7

    其中只有CMD3会和SD内存卡产生碰撞。因此我们只需要先将SD I/O卡初始化,然后禁用掉SD I/O卡的CMD3命令,再初始化SD内存卡,问题就解决了。

    当一张卡被CMD7命令选中时,将不能用CMD3来更换被选中卡的RCA地址。因此我们可以用CMD7来禁用CMD3。这样我们就可以在WiFi和SD卡的时钟都打开的情况下,成功完成初始化操作。但一定要在CMD引脚上接与门,因为SD卡在进行数据传输时(如CMD17命令读取扇区内容),WiFi模块的存在会导致数据传输出错(SDIO_STA_DCRCFAIL置位),并且在WiFi模块下载固件时使用CMD7命令切换到SD卡读取数据也会导致异常(WIFI_CARDSTATUS寄存器的值由0x0d变为0x05,导致固件无法继续下载)。


    由Part E1文档中的初始化流程图可知,用于初始化SD内存卡的CMD0和CMD8应放在用于初始化SD I/O卡的CMD5命令的前面。

    由于WiFi模块中MP(Memory Present)=0,所以不应在WiFi模块的CMD3命令前发送ACMD41和CMD2,应该改在CMD3后面发送。

    最终的初始化顺序为:

    CMD0 -> CMD8 -> CMD5 -> CMD5 -> CMD3 -> CMD7 -> ACMD41 -> CMD2 -> CMD3

    其中加粗的部分为WiFi模块的初始化命令。


    【程序2】Marvell 88W8686 WiFi模块与SD内存卡同时插在SDIO总线上的初始化代码:http://blog.csdn.net/zlk1214/article/details/76768218

    程序中定义了两个存放RCA地址的全局变量,第一个用于存放SD内存卡的RCA地址,第二个用于存放WiFi模块的RCA地址。若没有插入SD卡,则rca_sd=0。

    [cpp] view plain copy
    1. uint16_t rca_sd, rca_wifi;  
    程序不允许不插入WiFi模块,若没有插入WiFi模块,程序将卡死在Card_CheckCommand函数中不停地重发CMD5命令,初始化永远无法完成。

    程序中新增了一个Card_Select函数,专门用于发送CMD7命令,选择要操作的卡。

    [cpp] view plain copy
    1. void Card_Select(uint16_t rca)  
    2. {  
    3.     SDIO->ARG = rca << 16;  
    4.     SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 7;  
    5.     while (SDIO->STA & SDIO_STA_CMDACT);  
    6.     if (SDIO->STA & SDIO_STA_CMDREND)  
    7.     {  
    8.         SDIO->ICR = SDIO_ICR_CMDRENDC;  
    9.         printf("Card 0x%04x selected! status=0x%08x\n", rca, SDIO->RESP1);  
    10.     }  
    11.     else if (SDIO->STA & SDIO_STA_CTIMEOUT)  
    12.     {  
    13.         SDIO->ICR = SDIO_ICR_CTIMEOUTC;  
    14.         printf("Card not selected! CMD7 timeout!\n");  
    15.     }  
    16. }  

    若参数rca为0,则取消选中所有的卡,CMD7命令将不会产生回应,进入的分支是SDIO_STA_CTIMEOUT。

    注意:当rca!=0时,CMD7命令也有可能超时,所以最好加上命令超时重发的代码。

    SD卡和SD I/O卡设置总线宽度的命令是不一样的。前者用的是ACMD6命令,后者用的是CMD52命令。

    [cpp] view plain copy
    1. /* 发送CMD52, 设置WiFi模块的总线宽度 */  
    2. Card_Write(0, 0x07, Card_Read(0, 0x07) | 0x02); // Bus Width: 4-bit bus  
    3.   
    4. /* 发送ACMD6, 设置SD内存卡的总线宽度 */  
    5. if (rca_sd)  
    6. {  
    7.     Card_Select(rca_sd); // 使用CMD7命令选中SD内存卡  
    8.     SDIO->ARG = rca_sd << 16; // 这回CMD55是发给SD内存卡的  
    9.     SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 55;  
    10.     while (SDIO->STA & SDIO_STA_CMDACT);  
    11.     SDIO->ICR = SDIO_ICR_CMDRENDC;  
    12.     SDIO->ARG = 2; // ACMD6的参数: 4-bit bus  
    13.     SDIO->CMD = SDIO_CMD_CPSMEN | SDIO_CMD_WAITRESP_0 | 6; // SET_BUS_WIDTH  
    14.     while (SDIO->STA & SDIO_STA_CMDACT);  
    15.     if (SDIO->STA & SDIO_STA_CMDREND)  
    16.     {  
    17.         SDIO->ICR = SDIO_ICR_CMDRENDC;  
    18.         printf("Memory bus width is changed! status=0x%08x\n", SDIO->RESP1);  
    19.     }  
    20. }  
    21.   
    22. SDIO->CLKCR |= SDIO_CLKCR_WIDBUS_0; // 将STM32上的SDIO外设设为4位数据宽度  
    23. Card_Select(rca_wifi); // 选中WiFi模块  
    展开全文
  • 51单片机驱动WiFi模块ESP8266模块的代码,ESP8266模块是网上一款比较普及的wifi模块
  • 单片机控制WIFI模块

    2020-07-29 14:20:06
    本人工作实践中写的源代码,注释比较仔细,用于单片机控制WIFI模块的通信
  • 基于51单片机wifi智能led灯的毕业设计

    千次阅读 多人点赞 2020-02-13 12:01:53
    基于51单片机wifi智能led灯的毕业设计 摘要 系统基于STC89C52RC单片机设计,拥有自动与手动两种模式,自动模式下可以过热释红外传感器检测是否有人,采用光敏电阻构成的电路检测环境光的强度,从而自动实现灯的...

    基于51单片机的wifi智能led灯的毕业设计

    摘要

          系统基于STC89C52RC单片机设计,拥有自动与手动两种模式,自动模式下可以过热释红外传感器检测是否有人,采用光敏电阻构成的电路检测环境光的强度,从而自动实现灯的自动开启和关闭;手动模式下可以使用wifi连接手机,通过手机app手动控制不同灯的开启和关闭并可控制各LED灯的光亮度,并采用可移动充电式电源供电,满足不同场景的需求。系统简单易行、控制方便,可用于传统照明的节能改造。

    设计思路

          本系统基于STC89C52单片机设计,可实现灯具的自动控制;同时,结合esp8266wifi模块通过安卓手机端app与wifi模块进行数据通信,实现对被控对象的无线连接手动控制。主要功能如下:

    (1)自动模式: 通过光敏电阻和热释红外传感器分别自动检测光的强弱和是否有人,室内无人或者光照充足时自动灭灯,灭灯时间延时一分钟;有人到且光照不足时自动开灯;该模式即节约了人力资源又节约了大量电能。
    (2)手动模式:开启手机wifi,通过手机APP手动控制灯的开启和关闭,使用手机轻松控制灯的开关,方便快捷。

    图1 系统框图
    在这里插入图片描述
    在这里插入图片描述

    硬件设计

    wifi模块

          wifi模块采用esp8266,对wifi模块进行配置就需要使用到AT指令集。其最基础的一些AT指令集在下面贴出,部分会在代码中使用到。

    WIFIAT+CIPMUX=1AT+CIPSERVER=1,80	//修改端口号
    AT+CWMODE=3				//设置模式
    AT+RST					//重启
    AT+CWJAP="WiFixdd","xiaojieying"	//搜索并连接路由器
    AT+CIPMODE=1				//透传
    AT+CIPMUX=0				//单路模式
    AT+CIPSTART="TCP","172.29.242.2",8080	//连接手机端
    AT+CIPSEND				//进入透传

    若想深入研究自行找资料学习AT指令集。

    热释电传感器

          HC-SR501 人体红外传感器模块是基于红外技术的自动控制模块,它可以检测人或某些动物发出的红外辐射并输出电信号我们使用的 。
    在这里插入图片描述
    在这里插入图片描述

    电源模块

          电源模块采用18650可充电电池,由于其供电电压为3.6V,不能很好的为电路工作,所以我们通过升压模块对其进行电压放大,以达到标准供电电压5V。并能通过变压充电模块为18650电池充电。
    在这里插入图片描述

    驱动电路

          由于52单片机的IO口驱动能力较弱,无法满足大型LED灯的需要,所以我们在这里通过三极管进行电流放大,该驱动电路通过电阻对单片机IO口进行降压,使得三极管得以导通,并对单片机IO口电流进行放大,以达到驱动该大型灯泡的能力,并给每一个LED灯串联一个限流电阻,已达到保护LED灯延长使用寿命的作用。该电路共使用六个大型LED灯照明,三黄三白,不同颜色交替摆放,已保证单种颜色灯光的照明范围。
    在这里插入图片描述
    要注意的是实物硬件电路板对led灯的一个布局,以及合理跳线的一个问题。

    代码实施

          程序主要模块是通过单片机串口与蓝牙进行通信。由于STC89c52没有独立的波特率发生器,所以我们这里将定时计数器T1改装成波特率发生器,产生9600的标准波特率,并通过串口中断函数对读取数据进行接收,并将串口中断优先级调到最高。
    在这里插入图片描述
          下面是手机APP与单片机的简单通信协议,创建单独协议的目的是为了对以后项目扩展,技术升级留下足够的预留空间,并可以保证产品的加密安全性。
    在这里插入图片描述
          通过对程序的算法设计,该智能灯泡可达到黑夜可自动控制,有人经过则自动开起,并保持最大可调时间10分钟后自动熄灭。通过手机APP控制LED的开关以及亮度调节,若夜晚通过手机APP开起灯光,人经过后无需手机关闭即可自动关闭,以防止由于忘关造成的电力浪费。以下是主程序流程图。
    在这里插入图片描述
    main.c

    #include "reg52.h"
    #include"uart.h"
    #include"led.h"
    #include<stdio.h> 
    
    sbit MAN=P1^4;  
    
    int main()
    { 	 	
      unsigned char dat=0;	
      NewLineReceived = 0;	
      MAN=1;	
      ColorLED_Init();		//LED灯初始化  
      Serial_Init();		//串口初始化  
      ms_delay(1000);	
      WIFI_Init();			//WIFI模块初始化	    	
      timer0_init();		//定时计数器零初始化	
      color_led_pwm(0, 0);  
      while(1)	
      { 			
        if(MAN==0)		
        {			
          color_led_pwm(255, 255);			
          while(MAN==0) ;				
          color_led_pwm(0, 0);		
        }  	 			
        if(NewLineReceived == 1)		
        {	 			
          uart_send_string("Init OK!"); 			
          serial_led();//调用串口解析函数			
          NewLineReceived = 0;		 		
        }	
      }  
    }
    

    led.c

    #include "reg52.h"
     sbit LED_W = P1^0;
     sbit LED_W1=P1^1;
     sbit LED_W2=P1^2; 
     sbit LED_Y = P1^5;
     sbit LED_Y1 = P1^6;
     sbit LED_Y2 = P1^7;
      unsigned char whitenum=0;
      unsigned char yellownum=0; 
      unsigned char ledWnum=0;
      unsigned char ledYnum=0;
       void ColorLED_Init()
       {	      	LED_W = 1;	LED_W1 = 1;	LED_W2 = 1;   	LED_Y = 1;	LED_Y1 = 1;	LED_Y2 = 1;}
     void color_led_pwm(unsigned char v_iwhite, unsigned char v_iyellow)//点亮相应颜色的灯
     {	whitenum = v_iwhite;	yellownum = v_iyellow; }
      /*** Function       ledRPwmWrite* @author        Danny* @date          2017.08.16* @brief         LED_R产生pwm* @param         void* @retval        void* @par History   无*/ 
     void ledWPwmWrite()
     {	if((whitenum != 0) && (whitenum!=255))	{		if(ledWnum <=  whitenum)		{				LED_W=1;			LED_W1=1;			LED_W2=1;		}		else		{				LED_W=0;			LED_W1=0;			LED_W2=0;		}	}	else if(whitenum==0)		{				LED_W=0;			LED_W1=0;			LED_W2=0;		}	else if(whitenum==255)	   	{				LED_W=1;			LED_W1=1;			LED_W2=1;		}} 
     /*** Function       ledGPwmWrite* @author        Danny* @date          2017.08.16* @brief         LED_G产生pwm* @param         void* @retval        void* @par History   无*/ 
     void ledYPwmWrite()
     {	if((yellownum != 0)&&(yellownum!=255))	{		if(ledYnum<=yellownum)		{			LED_Y=1;			LED_Y1=1;			LED_Y2=1;		}		else		{			LED_Y=0;			LED_Y1=0;			LED_Y2=0;		}	}	else if(yellownum==0)	   {			LED_Y=0;			LED_Y1=0;			LED_Y2=0;		}	else if(yellownum==255)	   {			LED_Y=1;			LED_Y1=1;			LED_Y2=1;		}}   
     void Update_ColorPWM()
     {	ledWPwmWrite();	ledYPwmWrite();} 
     void timer0_init()
     {	TMOD|=0X01;  //定时器T0工作方式1,定时器T1工作方式2    
     TH0=0XFF;    //100us定时,装入初值    
     TL0=0XA4;	TR0=1;		//启动T0工作    
     ET0=1;		//允许T0中断  	
     EA =1;	    //开总中断	
     PT0=0; }	
      void timer0() interrupt 1  
      {     TH0=0XFF;    //50us定时,装入初值   
       TL0=0XD2;  	//控制pwmled	
       ledWnum++;	ledYnum++;	Update_ColorPWM(); 
      }
       

    uart.c

    #include "reg52.h"
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
     #include"led.h"
     bit NewLineReceived = 0;       //串口接收完成标志位bit StartBit  = 0;             //协议开始标志int g_num=0;				   //定义变量int g_packnum=0;char InputString[50] = {0};    //用来储存接收到的内容 /*** Function       StringFind* @author        Danny* @date          2017.08.16    * @brief         字符串查找* @param[in]     pSrc:源字符串; pDst:查找的字符串; v_iStartPos:源字符串起始位置* @param[out]    void* @retval        void* @par History   无*/int StringFind(const char *pSrc, const char *pDst, int v_iStartPos)  {  	int i, j;      for (i = v_iStartPos; pSrc[i]!='\0'; i++)  	  //判断是否到了源字符串的结束符    {          if(pSrc[i]!=pDst[0])  					  //与所要查找的字符串的第一个字节作比较        	continue;                 j = 0;          while(pDst[j] !='\0' && pSrc[i+j]!='\0')  //判断是否到了所要查找字符串以及源字符串的结束符        {              j++;              if(pDst[j]!=pSrc[i+j])              break;          }          if(pDst[j]=='\0')  						  //判断是否到了所要查找字符串的结束符            return i;      }      return -1;  }  /*** Function       uart_send_byte* @author        Danny* @date          2017.08.16* @brief         串口发送一个字符* @param[in]     data:字符* @param[out]    void* @retval        void* @par History   无*/void uart_send_byte(unsigned char dat){   SBUF = dat;	          //把数据放到SBUF中   while(TI == 0);	      //未发送完毕就等待   TI=0;                  //发送完毕后,要把TI重置0} /*** Function       uart_send_string* @author        Danny* @date          2017.08.16* @brief         串口发送一个字符串* @param[in]     void* @param[out]    void* @retval        void* @par History   无*/void uart_send_string(unsigned char *str){	while(*str != '\0')		     //判断指针str是否指向字符串的末尾	{	   uart_send_byte(*str);     //往串口上发送字符串	   str++;					 //指针自增,按照一个字节一个字节发送字符串	}}				   /*** Function       Serial_Init* @author        Danny* @date          2017.08.16* @brief         串口初始化函数* @param         void* @retval        void* @par History   无*/void Serial_Init(){  	SCON=0x50;      //[bit6:5]SM1 SM2 = 1 0;[bit4]REN=1  /*	AUXR=0x11;      //[bit4]BRTR=1,允许独立波特率发生器运行;[bit0]SIBRS=1,                    //独立波特率作为串口1的波特率发生器,此时定时器1释放	BRT=0XFD;       //独特波特率发生器定时器(产生波特率9600) */	TMOD|=0X20;		PCON=0;	TH1=TL1=0XFD;	TR1=1;  	ES=1;			//开串口中断  	EA=1;			//开总中断	PS=1;} /*** Function       serial_IRQHandler* @author        Danny* @date          2017.08.16* @brief         串口中断处理接收串口数据函数* @param         void* @retval        void* @par History   无*/void serial_IRQHandler(void) interrupt 4 using 1  //串行口中断,第一组寄存器{	 	unsigned char date = 0;   	if(RI)  	{    	RI=0;					       //清除接收中断标志位		date=SBUF;				       //串行口接收到的数据		if(date == '$')		{	  		StartBit = 1;		       //协议开始	  		g_num = 0;		}		if(StartBit == 1)		{			InputString[g_num]=date;   //将接收到的数据存储到定义的数组中		}		if(StartBit == 1 && date == '#')		{	  		NewLineReceived = 1;	   //串口接收完成标志	  		StartBit = 0;			   //协议关闭	  		g_packnum = g_num;		   		}		g_num++;					    //g_num自增		if(g_num >= 80)		{			g_num=0;				    //将g_num清零			StartBit=0;			NewLineReceived=0;		    //串口接收完成标志		}  	}}/******************************************************************函 数: void ms_delay(int t)功 能: 毫秒级延时参 数: 无返回值: 无*******************************************************************/void ms_delay(int t)  { 	int i,j; 	for(i=t;i>0;i--) 		for(j=110;j>0;j--); }/******************************************************************函 数: void WIFI_Init(void)功 能: wifi初始化(名字:esp8266;密码:1234567890)参 数: 无返回值: 无*******************************************************************/ void WIFI_Init(void) {	ES = 0;	TI = 1; 	printf("AT+RST\r\n");	ms_delay(1000) ;	printf("AT+CWMODE=3\r\n");	ms_delay(1000) ;	printf("AT+CIPMUX=1\r\n");	ms_delay(1000) ;	printf("AT+CIPSERVER=1,8080\r\n"); 	ms_delay(1000) ;printf("AT+CIOBAUD=9600\r\n"); // 设置与单片机一致的波特率 	ms_delay(1000) ;	while(!TI);	TI = 0;	ES = 1;} /*void WIFI_Init(void) {	ES = 0;	TI = 1; 	uart_send_string("AT+RST\r\n");	ms_delay(1000) ;	uart_send_string("AT+CWMODE=3\r\n");	ms_delay(1000) ;	uart_send_string("AT+CIPMUX=1\r\n");	ms_delay(1000) ;	uart_send_string("AT+CIPSERVER=1,8080\r\n"); 	ms_delay(1000) ;uart_send_string("AT+CIOBAUD=9600\r\n"); // 设置与单片机一致的波特率 	ms_delay(1000) ;	while(!TI);	TI = 0;	ES = 1;}  */void serial_led(){	 //解析蓝牙APP发来的灯控信息    //如:$JC,CLW255,CLY000# 亮白灯	if (StringFind((const char *)InputString, (const char *)"CLW", 0) > 0) //判断是否查找到了字符串"CLW" 	{		int m_kp, i, ii, white, yellow;								   //定义变量		char m_skp[5] = {0}; 		//寻找以CLW开头,,结束中间的字符											   //定义数组存放数据		i = StringFind((const char *)InputString, (const char *)"CLW", 0); //查找到字符串"CLW"的位置赋值给i		ii = StringFind((const char *)InputString, (const char *)",", i);  //查找到字符串","的位置赋值给ii		if (ii > i)														   //判断ii与i的关系大小		{						memcpy(m_skp, InputString + i + 3, ii - i -3);				//从InputString + i + 3所指的内存地址的起始位置开始拷贝(ii-i-3)个字节到m_skp所指的内存地址的起始位置中			m_kp = atoi(m_skp);											   //将找到的字符串m_skp转换成整形数赋值给m_kp			white = m_kp;		} 		 		//寻找以CLY开头,#结束中间的字符		i = StringFind((const char *)InputString, (const char *)"CLY", 0); //查找到字符串"CLY"的位置赋值给i		ii = StringFind((const char *)InputString, (const char *)"#", i);  //查找到字符串","的位置赋值给ii		if (ii > i)														   //判断ii与i的关系大小		{			memset(m_skp, 0x00, sizeof(m_skp));							   //清空m_skp中的数据			memcpy(m_skp, InputString + i + 3, ii - i -3);			//从InputString + i + 3所指的内存地址的起始位置开始拷贝(ii-i-3)个字节到m_skp所指的内存地址的起始位置中			m_kp = atoi(m_skp);											   //将找到的字符串m_skp转换成整形数赋值给m_kp			yellow = m_kp;			//sprintf(ReturnTemp, "%d", green);			//uart_send_string(ReturnTemp);			color_led_pwm(white, yellow);                              //点亮相应颜色的灯			NewLineReceived = 0;  			memset(InputString, 0x00, sizeof(InputString));               //清空串口数据						return;		}	}	if (StringFind((const char *)InputString, (const char *)"JC", 0) == -1 )  //判断是否查找到了字符串"4WD"	{		//点灯判断    	if (InputString[1] == 'W')       //白灯    	{			if(InputString[2]=='1')      		color_led_pwm(255, 0);			if(InputString[2]=='0')      		color_led_pwm(0, 0);    	}    	else if (InputString[1] == 'Y')  //黄灯    	{      		if(InputString[2]=='1')      		color_led_pwm(0, 255);			if(InputString[2]=='0')      		color_led_pwm(0, 0);    	}    	else if (InputString[1] == 'O')  //全部灯    	{      		if(InputString[2]=='1')      		color_led_pwm(255,255);			if(InputString[2]=='0')      		color_led_pwm(0, 0);    	}    		}
     }
     

    由于格式问题,代码格式是乱的,可自行整理,并添加.h文件,或通过下方链接下载。

    手机APP程序

          可自行制作或下载第三方软件WiFi调试助手。
    凭借其开放性的巨大优势,安卓平台在设备开发应用中广受欢迎。安卓平台允许开发者根据自己的喜好和应用需求,设计出具有不同特色的实用软件。同时,安卓平台还能够适配多种硬件开发平台,对于硬件开发门槛要求低,极大地方便了用户对其进行相关的开发研究。此外,凭借其巨大的优势,安卓平台在当前开发平台领域中呈现逐年上升的趋势。因此在本设计中采用安卓平台进行本项目的设计开发。

    注:需要源文件请到我个人主页下载,代码仅供参考。

    下载链接://download.csdn.net/download/weixin_44313435/12152854

    展开全文
  • 51单片机驱动无线局域网 WiFi 模块驱动C 源码和接口电路
  • 在传统观念里,单片机WIFI通信,尤其是单片机高速WIFI通信传输,是一个不现实的梦想,原因一般在于: (1)能做高速通信的WIFI模块,一般只是USB、SDIO、或PCIE之类的接口而大多数普通单片机都不会提供这些主机接口...

    在传统观念里, 单片机WIFI通信,尤其是单片机高速WIFI通信传输,是一个不现实的梦想,原因一般在于:
    (1) 能做高速通信的WIFI模块,一般只是USBSDIO、或PCIE之类的接口而大多数普通单片机都不会提供这些主机接口;
    (2)即使某些款的单片机也能提供这些接口,也会单片机和WIFI模块的通信编程变得非常复杂,同时也会造成单片机选型的成本增加;
    (3)普通单片机因为资源(性能和存储)有限,也难以支持实用的较好性能的单片机WIFI方案。

    所以,大多数用在单片机系统上的WIFI模块,要么是以串口为主的,只能做一些基本的低速控制命令的传输,或者费了九牛二虎之力,集成了USB、 SDIO、或PCIE接口的WIFI模块,其做出来的效果也很一般,速度一般也不超过300K字节每秒。因此,在普通单片机做高速WIFI通信,基本上成为了一个不可实现的梦想,并甚至成为一种传统惯性认识。

    其实,选择市面上某款基于SPI接口的高速WIFI模块,占用单片机资源少,在许多常见的单片机上都实现了高速传输,普通环境下,实测有效速度可以超过兆字节每秒,长时间运行不掉线、不丢包,在单片机音视频传输、高速采集数据传输场合都得到了超过了一年以上的产品化的验证。该方案功能强大、性能高、稳定,集成移植简单、适应面广,精致小巧。性价比超高。

    该方案提供支持多款常见单片机的验证测试好的例程包,包括但不限于ST系列、Nuvoton系列、NXP系列、Freescale K60系列、TI MSP430系列、Holtek HT32系列。还有技术支持,所以淘宝上的口碑评价不错! 

    更多详情可去某宝搜索”SPI 高速 多链接" 了解详情。
     

     

      

    ALK8266高速WIFI模组 SPI接口 带WEB网页 音视频传输产品化验证

    (在某宝搜索 “SPI 高速 多链接”  里面有更进一步的详细介绍,很技术化)

    1. 有效通信速度快,效率高,通信实时性好
       1.1 “实测的”“有效”吞吐速度高,超过M字节每秒(MBytes/s),所以可用来传输音视频或大量数据
       1.2 准实时性收发,连续发包之间的间隔可在ms级或us级,可适应一些对传输性实时性有要求的场合。
    2. 稳定可靠不丢包,长时间通信不掉线
       2.1 测试条件:普通办公室环境实测
       2.2 测试速度:速度稳定在兆字节每秒(MBytes/s)以上
       2.3 长时间运行测试:持续运行30天,不掉线、传输不停止/不卡死
       2.4 丢包测试:TCP通信持续测试过5小时以上,不丢包、不多包,不丢字节、不多多字节,收发方数据完全一样。
           测试场景包括:(1)TCPUDP抓包软件单纯发送, 模块单纯接收
                                   (2)TCPUDP抓包软件单纯接收, 模块单纯发送
                                   (3)TCPUDP抓包软件同时发收, 模块同时收发
                                   (4)两个WIFI模块之间互相对发对收
    3. 功能全面,使用灵活,使用场合适应面广
       3.1 灵活实用的无线通信
           3.1.1 模块支持 工作站STA, 热点AP 以及 STA+AP 混合模式,无论是否存在第三方热点,都可实现通信
           3.1.2 支持UDP,TCP客户端,TCP服务器,灵活方便
           3.1.3 UDP通信支持广播、组播以及单播,灵活高效
           3.1.4 支持多链接,每个链接独立随意配置,实用的多通道高速通信
           3.1.5 支持多客户端,模组作为TCP服务器可以同时和多个客户端通信
           3.1.6 支持大块数据阵发发送,适合一些文件等大块数据传输需求的场合
       3.2 内嵌WEB服务器,操作更灵活和便捷
           3.2.1 无需安装APP,直接通过常见的浏览器操作
           3.2.2 普通智能手机或电脑均可直接操作
           3.2.3 支持“自动弹出网页”功能
           3.2.4 可通过网页直接配网、配置热点、建立和查询链接通道信息等操作
       3.3 配网方式灵活多样方便
           3.3.1 WEB网页配网:勿需安装APP,直接输入,简单方便,受限条件少
           3.3.2 智能配网:SmartConfig/SmartLink, 微信Airkiss扫一扫;
                 -特色 提供配网进展灯闪烁样式,提高智能配网操作方便性。
           3.3.3 直接配网:串口AT指令、SPI接口API函数 直接输入
       3.4 内嵌RSA加密和签名算法
           3.4.1 可作为加密芯片使用,支持单片机固件的防复制
       3.5 支持有意义的低功耗
           3.5.1 确保有用发射功耗足够强传输距离足够远的前提下的低功耗机制
           3.5.2 支持休眠,自动唤醒或手动唤醒
           3.5.3 深度休眠电流低于1mA
     4. 封装灵活、尺寸小巧
     
     4.1 整孔和半孔(邮票孔)一体化设计
       4.2 兼容插件方式或贴片装配方式
       4.3 大小和一枚1角硬币相当
    5. 常见通用的SPI单片机主机接口,集成简单,占用单片机资源少
     
     5.1 主机接口为标准的SPI从,适配绝大多数的常见单片机,单片机选型范围广
           - 硬件接线简单,只需要标准的SPI总线管脚,无需额外的UART串口
           - 按照普通的SPI从机方式对模块进行读写,实现单片机与模块的配置查询以及传输通信
       5.2 模块上已集成了TCPIP协议栈,
           - 单片机无需再集成相关协议栈或操作系统,
           - 因此集成简单,占用单片机资源少,且通信效率也更高
    6. 提供单片机例程包、集成说明等资料文档,和技术支持
       6.1 单片机例程包都经过实际测试和验证
       6.2 集成说明文档和开发使用技巧,都是基于开发经验和客户反馈总结编写,非简单的协议复制
       6.3 可提供目前市场上主流的单片机例程包,包括但不限于:
           - STM32系列(如F1/F2/F3/F4/F7 H7 L1/L4)
           - NXP LPC17xx系列
           - NXP K60, K27/28系列、
           - NXP i.MX RT10xx系列、
           - C8051系列、
           - Nuvoton新塘 NUC123xx系列、M45x系列、
           - TI MSP430系列
           - TI C2000/C5000(如TMS320F28335)
           - 等
    在某宝搜索 “SPI 高速 多链接”  排名靠前的就是,里面有更进一步的详细介绍,很技术化。

     

    展开全文
  • 之前心血来潮花了近一个月制作了一台WiFi小车,功能不复杂主要是实现小车的...电机驱动模块主要是用于给电机供给稳定的电压,同时电机也是通过驱动模块与51单片机相连,进而能通过51单片机控制电机的正转与反转。...
  • 51单片机+ESP8266-01WIFI模块实现数据传输

    万次阅读 多人点赞 2019-04-09 12:17:13
    51单片机开发板 ESP8266WIFI模块 USB转TTL 22.1184MHZ的晶振 串口调试助手 网络串口调试助手 ESP8266接线 管脚号 管脚 功能 管脚号 管脚 功能 1 VCC 3.3V电压源 5 GND 接地线 2 GPIO16 悬空 6 GPIO2...
  • 芯片内集成了完整的网络协议,使网络连接到现有的嵌入式系统中而不增加中控芯片的负担。
  • 基于8266WIFI模块实现智能手机与51单片机的通信入门

    万次阅读 多人点赞 2018-09-25 15:07:46
    1.1 8266WIFI模块  在文章的最开始我们首先要介绍今天的主角,8266WIFI模块。这是一个非常好用的模块,其他的废话不在多说,我们直接上干货.    这是一张我从百度上盗的一张8266WIFI模块的原理图,虽然8个引脚...
  • 本程序是从STM32F103ZE单片机驱动的88W8801程序(20180807版)移植过来的。 本程序所用的单片机型号为:STM32F407VE PD14端口为复位引脚(PDN),请务必连接!不连接PDN引脚将导致固件无法下载!如果模块...
  • 之前做了一个andriod通过Wifi控制单片机的app,最近没事传上来,顺便赚点积分 app主要是界面更加友好,界面上有一个触控摇杆,四个拖动条(0-100),四个开关和四个按钮,作为控制,做遥控小车或者其他的手机端控制...
  • 51单片机驱动ESP8266

    千次阅读 2019-05-28 14:45:08
    就是单片机。 关于ESP8266你必须知道的 博主使用的是安信可的产品,12S 常用AT指令: (1) 设置模块为ST模式:发送:AT+CWMODE=1;返回:OK (2) 查看当前WiFi列表:AT+CWLAP返回: OK (3) 加入当前无线网络:...
  • main.c #include <reg52.h>...#include "lcd1602.h" /* 1602显示驱动文件 调用init和显示字符串2个函数 */ #include "ds18b20.h" #include <stdio.h> #include <stdlib.h> #includ.
  • ESP8266 WIFI串口通信模块应该是使用最广泛的一种WIFI模块之一了。为什么呢? 因为ESP8266模块是一款高性能的WIFI串口模块,可以不用知道太多WIFI相关知识就可以很好的上手。说白了,只是个WIFI转串口的设备,你...
  • 上一篇文章我们已经通过三条线索简单地描述了wifi驱动的框架,现在我们开始深入到每条线索中。首先我们从USB设备这条线索开始。在分析之前,我们需要理解在整个wifi模块中,USB充当什么角色?它的作用是什么?实质上...
  • 成品展示部分 :(ESP8266只是其中一个小部分而已) 实物图: 基于互联网的农业大棚环境监控系统设计 ...(在看代码之前务必先串口调试ESP8266,否则难以用单片机控制ESP8266通过互联网实现数据远程传...
  • 单片机控制打印机

    2020-07-29 14:18:51
    这个是一个项目上得测试代码,用51单片机 发票打印机 51 串口接收pc数据 51的io连接并口 ,中间接一个74hc05 绝对原创资料 请勿外传 仅供学习用 内含 vb6.0 上位源码 51 源码 keil编译器
  • 逻辑图下面是完整的程序 (io口模拟spi发送数据写给rom)个人理解 水平有限 头文件 lcd.h#ifndef __LCD__H__ #define __LCD__H__ #include&lt;reg51.h&gt; #include&lt;intrins.h&...
1 2 3 4 5 ... 20
收藏数 1,556
精华内容 622
关键字:

单片机怎么驱动wifi