ATWINC1500模块是一个低功耗的物联网模块。它的官方页面如下,
https://www.microchip.com/en-us/product/ATWINC1500
其中芯片的官方页面如下,
https://www.microchip.com/en-us/product/ATWINC1500-IC
这里我们重点介绍它的移植,关于它的介绍,大家可以参考官方网页中的如下这两篇文档,
在模块的官方页面中,有如下一篇文档,
这篇文档就是教大家如何把1500移植到单片机中,我们这篇文章也是根据这篇文档进行移植的。
打开这篇文档,首先我们需要安装MLA框架,
点击上面的标红的link进入如下链接,
Microchip Libraries for Applications (MLA) | Microchip Technology
接下来进行下载,
下载完成后,安装包如上,安装过程就不在这里说了,因为没什么特殊的,基本都是下一步就好。
下载完成后,大家可以在如下路径下看到1500的驱动代码。
这些驱动代码在后续步骤中需要我们拷贝到具体工程中。
接下来就开始我们正式的移植工作了,首先我们看下1500模块和单片机接口,如下,
除此之外,要驱动1500需要的资源在文档中也列出了,
那么具体SPI接口、定时器、中断应该如何配置?别急,这个我们后面会介绍。
接下来我们用MCUXpressoIDE导入一个SDK example,这里我们选择以轮询方式实现SPI通信的例程导入,
然后将1500驱动代码文件夹复制到我们所建工程的路径下,如下,
接下来打开winc1500_driver_config.h文件,按照手册我们需要配置HOST_MCU_BIG_ENDIAN和M2M_POINTER_SIZE_IN_BYTES,
根据1020手册我们知道这款芯片只支持小端模式,所以HOST_MCU_BIG_ENDIAN保持注释状态;而M2M_POINTER_SIZE_IN_BYTES只需要按照注释运行int x = sizeof(int *),然后按照x的取值进行定义即可。发现x为4。所以,
然后根据文档我们创建一个wf_mcu_driver_stub.c的源文件,
然后我们会在这个文件中实现一些函数,这些函数和具体使用的单片机相关的并且驱动代码会调用这些函数,它们被称为桩函数,也就是说这些函数的声明已经在驱动代码中写好了,格式也规定好了,我们只需要实现它。接下来我们需要把这些桩函数全部实现!
首先,我们需要实现SPI接口的读写函数。根据文档,我们选择SPI的模式0并且SPI时钟设置8M。
在SPI的初始化代码中,我们可以通过TRANSFER_BAUDRATE宏定义修改时钟频率。
而SPI的模式0,也在如下代码中配置了,
初始化SPI结束后,我们就可以实现SPI读写函数了,如下,
void m2mStub_SpiTxRx(uint8_t *p_txBuf,
uint16_t txLen,
uint8_t *p_rxBuf,
uint16_t rxLen){
status_t status;
if(txLen){
/*Start master transfer, transfer data to slave.*/
masterXfer.txData = p_txBuf;
masterXfer.rxData = NULL;
masterXfer.dataSize = txLen;
masterXfer.configFlags =
EXAMPLE_LPSPI_MASTER_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous | kLPSPI_MasterByteSwap;
status=LPSPI_MasterTransferBlocking(EXAMPLE_LPSPI_MASTER_BASEADDR, &masterXfer);
if(status!=kStatus_Success)
PRINTF("SPI WRITE FAIL\r\n");
}
if(rxLen){
/* Start master transfer, receive data from slave */
masterXfer.txData = NULL;
masterXfer.rxData = p_rxBuf;
masterXfer.dataSize = rxLen;
masterXfer.configFlags =
EXAMPLE_LPSPI_MASTER_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous | kLPSPI_MasterByteSwap;
status=LPSPI_MasterTransferBlocking(EXAMPLE_LPSPI_MASTER_BASEADDR, &masterXfer);
if(status!=kStatus_Success){
PRINTF("SPI READ FAIL\r\n");
PRINTF("status=%d\r\n",status);
}
}
}
这里的LPSPI_MasterTransferBlocking函数是一个阻塞函数,也就是该函数只有在传输完成后才会返回,但这里注意,在实际测试发现,连续调用这个函数读数据时,会出现总线忙的返回,如下,
所以我在该函数的读操作最后加了如下红框的判断,
也就是完成传输标志位拉高后再返回。
接下来我们需要实现定时器的桩函数,该定时器的精度是1ms,并且其中有一个32位的变量作为计数器,该计数器每隔1ms加一,当加到0xffffffff再返回到0,重新自增。该函数返回这个计数器。
我们这个轮询方式实现SPI的例程中,包含一个定时器,SysTick,我们就用这个定时器来做,在代码中已经将该定时器设定成了每一秒进一次中断,如下,
并且也已经有一个32位的无符号变量作为计数器,
我们只要在定时器中断函数中对g_systickCounter进行自增并且在定时器的桩函数中返回该计数器变量即可,
接下来我们实现GPIO的桩函数,根据文档我们需要实现三个GPIO的桩函数,分别控制CE、RESET、SPI的SS,这里SPI接口的SS引脚已经由SPI接口控制,所以控制SS的桩函数我们不用实现,但同时我们需要在驱动代码中删掉调用该函数的代码。如下,
GPIO的初始化代码如下,
/* Init CE */
sw_config.direction=kGPIO_DigitalOutput;
sw_config.interruptMode=kGPIO_NoIntmode;
sw_config.outputLogic=0;
GPIO_PinInit(CE_GPIO, CE_GPIO_PIN, &sw_config);
/* Init RESET */
sw_config.direction=kGPIO_DigitalOutput;
sw_config.interruptMode=kGPIO_NoIntmode;
sw_config.outputLogic=0;
GPIO_PinInit(RESET_GPIO, RESET_GPIO_PIN, &sw_config);
桩函数代码如下,
void m2mStub_PinSet_SPI_SS(t_m2mWifiPinAction action){
}
void m2mStub_PinSet_RESET(t_m2mWifiPinAction action)
{
if (action == M2M_WIFI_PIN_LOW)
{
GPIO_PinWrite(RESET_GPIO, RESET_GPIO_PIN, 0U);
}
else
{
GPIO_PinWrite(RESET_GPIO, RESET_GPIO_PIN, 1U);
}
}
void m2mStub_PinSet_CE(t_m2mWifiPinAction action)
{
if (action == M2M_WIFI_PIN_LOW)
{
GPIO_PinWrite(CE_GPIO, CE_GPIO_PIN, 0U);
}
else
{
GPIO_PinWrite(CE_GPIO, CE_GPIO_PIN, 1U);
}
}
接下来我们实现中断的桩函数,根据文档我们需要一个下降沿触发的中断,
所以我们在中断初始化时,设置中断模式为下降沿触发,
/* Init IRQN */
sw_config.direction=kGPIO_DigitalInput;
sw_config.interruptMode=kGPIO_IntFallingEdge;
sw_config.outputLogic=0;
EnableIRQ(GPIO1_Combined_0_15_IRQn);
GPIO_PinInit(BOARD_INITPINS_wifi_irq_GPIO, BOARD_INITPINS_wifi_irq_GPIO_PIN, &sw_config);
这里我们用GPIO1的5引脚作为中断引脚,根据1020手册,该中断是在GPIO1的联合中断中处理的,
根据文档要求,单片机的中断函数需要调用m2m_EintHandler(),
所以我们实现的中断处理函数如下,
void GPIO1_Combined_0_15_IRQHandler(void)
{
/* clear the interrupt status */
GPIO_PortClearInterruptFlags(BOARD_INITPINS_wifi_irq_GPIO, 1U << BOARD_INITPINS_wifi_irq_GPIO_PIN);
m2m_EintHandler();
SDK_ISR_EXIT_BARRIER;
}
禁止和使能中断的桩函数如下,
void m2mStub_EintEnable(void){
GPIO_PortEnableInterrupts(BOARD_INITPINS_wifi_irq_GPIO, 1U << BOARD_INITPINS_wifi_irq_GPIO_PIN);
}
void m2mStub_EintDisable(void){
GPIO_PortDisableInterrupts(BOARD_INITPINS_wifi_irq_GPIO, 1U << BOARD_INITPINS_wifi_irq_GPIO_PIN);
}
代码部分先告一段落,接下来大家不要忘了分配引脚,
对于SPI引脚和UART引脚,他们的配置我们不需要动,对于三个GPIO引脚,我们可以参考如下两个SDK example去配置,这里就不详细说了,
另外点开winc1500_api.h文件,这里我们需要把dprintf设置成我们调试的打印函数PRINTF,如下,
然后我们需要把官方提供给我们的demo复制到我们的工程目录下,这部分参考下面的官方文档,
这篇文档也是在模块的官方页面中。官方提供的demo在如下路径下,
我们直接把src目录拷贝到工程的source路径下,
接下来要把winc1500文件夹作为源代码路径,编译的时候需要包括进去,如下,
并且要在include路径中添加我们的winc1500文件夹,src文件夹及其子文件夹,也就是但凡文件夹中由h文件的都需要加入,
然后我们编辑main.c文件,注意要把winc1500_api.h加入到文件中,
文档中也提供了一个运行demo的main.c文件的模板,
也就是说,我们要运行demo,main文件大体就长这个样,当然我们也要根据具体情况做出调整,我的main文件如下,供大家参考,
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "fsl_lpspi.h"
#include "pin_mux.h"
#include "board.h"
#include "fsl_gpio.h"
#include "myheader.h"
#include "fsl_common.h"
#if ((defined FSL_FEATURE_SOC_INTMUX_COUNT) && (FSL_FEATURE_SOC_INTMUX_COUNT))
#include "fsl_intmux.h"
#endif
#include "winc1500_api.h"
#include "demo_config.h"
/*******************************************************************************
* Variables
******************************************************************************/
lpspi_transfer_t masterXfer;
volatile uint32_t g_systickCounter = 0U;
gpio_pin_config_t sw_config;
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Code
******************************************************************************/
void SysTick_Handler(void)
{
if (g_systickCounter == 0xffffffff)
{
g_systickCounter=0;
}else
g_systickCounter++;
}
void board_init(){
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
}
void spi_init(){
/*Set clock source for LPSPI*/
CLOCK_SetMux(kCLOCK_LpspiMux, EXAMPLE_LPSPI_CLOCK_SOURCE_SELECT);
CLOCK_SetDiv(kCLOCK_LpspiDiv, EXAMPLE_LPSPI_CLOCK_SOURCE_DIVIDER);
uint32_t srcClock_Hz;
lpspi_master_config_t masterConfig;
/*Master config*/
LPSPI_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate = TRANSFER_BAUDRATE;
masterConfig.whichPcs = EXAMPLE_LPSPI_MASTER_PCS_FOR_INIT;
srcClock_Hz = LPSPI_MASTER_CLK_FREQ;
LPSPI_MasterInit(EXAMPLE_LPSPI_MASTER_BASEADDR, &masterConfig, srcClock_Hz);
}
void SysTick_init(){
if (SysTick_Config(SystemCoreClock / 1000U))
{
while (1)
{
}
}
}
void gpio_init(){
/* Init IRQN */
sw_config.direction=kGPIO_DigitalInput;
sw_config.interruptMode=kGPIO_IntFallingEdge;
sw_config.outputLogic=0;
EnableIRQ(GPIO1_Combined_0_15_IRQn);
GPIO_PinInit(BOARD_INITPINS_wifi_irq_GPIO, BOARD_INITPINS_wifi_irq_GPIO_PIN, &sw_config);
/* Init CE */
sw_config.direction=kGPIO_DigitalOutput;
sw_config.interruptMode=kGPIO_NoIntmode;
sw_config.outputLogic=0;
GPIO_PinInit(CE_GPIO, CE_GPIO_PIN, &sw_config);
/* Init RESET */
sw_config.direction=kGPIO_DigitalOutput;
sw_config.interruptMode=kGPIO_NoIntmode;
sw_config.outputLogic=0;
GPIO_PinInit(RESET_GPIO, RESET_GPIO_PIN, &sw_config);
}
int main(void)
{
board_init();
gpio_init();
spi_init();
SysTick_init();
m2m_wifi_init();
while (1)
{
ApplicationTask();
m2m_wifi_task();
}
}
另外,官方给我们提供了很多demo,我们具体想要运行哪个,需要在demo_config.h中规定,
默认是运行检测ap的demo。大家在编译之前,根据情况决定是否将ApplicationTask函数中的printf换成PRINTF,防止打印不出信息。另外有些头文件和全局变量的声明大家也要注意,不然编译报错,诸如下图所示。
如果没有问题的话,终端会打印出如下内容。