请选择 进入手机版 | 继续访问电脑版
查看: 202|回复: 0

[技术交流] LLCC68模组基础代码移植

[复制链接]

46

主题

77

帖子

504

积分

利尔达员工

Rank: 9Rank: 9Rank: 9

积分
504
发表于 2022-6-14 11:16:10 | 显示全部楼层 |阅读模式
  背景

  我司提供的LLCC68模组的拉距实例代码库中使用了硬件SPI,定时器和中断。有时客户没有正确移植后会出现初始化不通过,SPI读写错误,不能收发数据包,DIO中断无反应等异常等。此类问题多是没有正确移植代码造成的。本文中的代码移植方式,不使用硬件SPI,不使用定时器,不使用中断,只使用最基础的IO的输入输出。如果客户移植代码后出现前边描述的异常,请按照本文基础方式修改代码后验证异常是否消失,确认模组本事没有问题后,再加入硬件SPI,定时器和中断。本文这种移植操作主要目的为帮助模组使用者确认模组确实可以正常工作。文中讲解力求简单明了通俗易懂易操作,便于头一次使用模组的MCU基础小白使用者阅读操作。

  过程

  一、IO输入输出测试

  此时先不要把MCU和模组连接再一起,单独的就选两个IO验证IO的输入输出是否都是正常的。这里选了一个STM32F103的最小系统板子来操作。
  如下是板子的原理图。
  先选取PC13(带LED灯)做输出,PB11做输入。

  这里先对IO的模式的几种配置进行一下说明:
  GPIO_Mode_AIN ------------------模拟输入
  GPIO_Mode_IN_FLOATING--------输入浮空
  GPIO_Mode_IPD ------------------输入下拉
  GPIO_Mode_IPU ------------------输入上拉
  GPIO_Mode_Out_OD -------------开漏输出
  GPIO_Mode_Out_PP --------------推挽输出
  GPIO_Mode_AF_OD --------------开漏复用功能
  GPIO_Mode_AF_PP ---------------推挽式复用功能

  代码上先完成两个IO的配置
    /////IO 输出 配置
    GPIO_InitTypeDef GPIO_InitStructure;//声明一次即可
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOC, GPIO_Pin_13);// PC13=0 LED亮
    GPIO_SetBits(GPIOC, GPIO_Pin_13);// PC13=0 LED灭
    /////IO 输入 配置
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启按键端口PB的时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //端口配置为上拉输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化端口
    GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);//PB11 取值

  然后while(1)里加上PC13(带LED灯)随着PB11输入不同电平变化的代码。
  while(1)
  {
        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 1 )//如果PB11输入是1
        {   
            GPIO_SetBits(GPIOC, GPIO_Pin_13); //PC13输出1,LED熄灭
       }
      else//如果如果PB11输入是0
      {
           GPIO_ResetBits(GPIOC, GPIO_Pin_13);//PC13输出0,LED亮
      }
  }

  二、规划模组和板子的接线

  此时也不要把模组和板子连接在一起,等软件全部移植好,编译完全OK后在把模组和板子接到一起。

  这里先只规划模组和板子的接线,确认需要用那些IO,确认那些IO需要用输入模式,那些IO需要用输出模式。

  如下是接线规划图
  由接线规划图,得到
  PA0 = SI --- OUT
  PA1 = SCK --- OUT
  PA5 = SW2 --- OUT
  PA6 = NSS --- OUT
  PA7 = SW1 --- OUT
  PB1 = RST --- OUT
  PA2 = SO --- IN
  PA3 = BUSY --- IN
  PA4 = DIO1 --- IN
  PB0 = DIO2 --- IN

  三、按照接线规划初始化所有的需要用到的IO

  先看下如下一个图示信息,这些图示信息可以让我们知道我们所用到的IO初始化后输出的IO应该初始化为高电平还是低电平。
  由图可知所有的OUT的IO初始化后输出的电平应为如下方式
  PA0 = SI --- OUT = 0或者1都行
  PA1 = SCK --- OUT = 0
  PA5 = SW2 --- OUT = 0
  PA6 = NSS --- OUT = 1
  PA7 = SW1 --- OUT = 0
  PB1 = RST --- OUT = 1

  其中SW1和SW2控制的是模组内的高频开关的状态,上电我们默认先把高频开关处于关闭状态,所以初始化后SW1和SW2都先输出0.
  所以初始化所有的IO如下:
  // PA0 =  SI   OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_0);//SI=1
    GPIO_ResetBits(GPIOA, GPIO_Pin_0);//SI=0
  // PA1 =  SCK  OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_1);//SCK=1
    GPIO_ResetBits(GPIOA, GPIO_Pin_1);//SCK=0
  // PA5 =  SW2  OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_5);//SW2=1
    GPIO_ResetBits(GPIOA, GPIO_Pin_5);//SW2=0
  // PA6 =  NSS  OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOA, GPIO_Pin_6);//NSS=0
    GPIO_SetBits(GPIOA, GPIO_Pin_6);//NSS=1
  // PA7 =  SW1  OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_7);//SW1=1
    GPIO_ResetBits(GPIOA, GPIO_Pin_7);//SW1=0
  // PB1 =  RST  OUT
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);//RST=0
    GPIO_SetBits(GPIOB, GPIO_Pin_1);//RST=1
  // PA2 =  SO   IN
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启按键端口PB的时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //端口配置为上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化端口
    GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2);//SO取值
  // PA3 =  BUSY IN
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启按键端口PB的时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //端口配置为上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化端口
    GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3);//BUSY取值
  // PA4 =  DIO1 IN
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启按键端口PB的时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //端口配置为上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化端口
    GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4);//DIO1取值
  // PB0 =  DIO2 IN
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启按键端口PB的时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //端口配置为上拉输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化端口
    GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0);//DIO2取值

  然后参考"一、IO输入输出测试"中的方式测试验证所有的需要用到的如上的IO的输入和输出都是好使的,此处不做详述,参考"一、IO输入输出测试"中的方式测试验证即可。

  四、把我司LLCC68的4个库文件加入工程

  把我们的官方例程库的4个文件加入到前边完成的这个STM32F103的工程中,然后查找清除BUG,此时也不要急着接模组,先把软件移植到没有BUG后再接模组。

  需要移植加入的4个文件为如图红色箭头起始出的几个文件。
  加入后如下图。
  五、替换修改最底层的SPI读写函数

  先把库中的SPI读写的最底层函数的硬件SPI的读写方式清空。

  然后改成软件IO的模拟的SPI读写方式。

  改后的结果如下:
  unsigned char SX126X_ReadWriteByte(unsigned char data)
  {
      unsigned char i;
      unsigned char onebyte;
      onebyte = data;
      for(i = 8; i > 0; i--)
      {
          __NOP();//__no_operation();
          if(onebyte & 0x80)
          {
              GPIO_SetBits(GPIOA, GPIO_Pin_0);  /////////LSD_SPI_SIMO_OUT(1);
          }
          else
          {
              GPIO_ResetBits(GPIOA, GPIO_Pin_0);  ////////LSD_SPI_SIMO_OUT(0);
          }
          __NOP();//__no_operation();
          GPIO_SetBits(GPIOA, GPIO_Pin_1);////////////LSD_SPI_SCK_OUT(1);
          __NOP();//__no_operation();
          onebyte <<= 1;      // next bit
          if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2) == 1)
          {
              onebyte++;      // 1 found on MISO
          }
          else
          {
                  __NOP();//__no_operation();
          }
          __NOP();//__no_operation();
          GPIO_ResetBits(GPIOA, GPIO_Pin_1);//////////LSD_SPI_SCK_OUT(0);
          __NOP();//__no_operation();
      }
      return onebyte;         //
  }

  然后再不调用任何LLCC68库函数测前提下,开始查找,修改BUG,直到编译没有错误为止。此处繁琐,请参考视频,和使用者实际的MCU情况进行操作。

  六、调用LLCC689的初始话函数

  先只调用LLCC689的初始话函数,然后继续修改BUG,直到编译没有错误为止。此处繁琐,请参考视频,和使用者实际的MCU情况进行操作。

  调用的函数如下
        //配置各个参数
      G_LoRaConfig.LoRa_Freq = Fre[0];      //中心频率470MHz
      G_LoRaConfig.BandWidth = LORA_BW_125;    //BW = 125KHz  BW125KHZ
      G_LoRaConfig.SpreadingFactor = LORA_SF9;  //SF = 7
      G_LoRaConfig.CodingRate = LORA_CR_4_6;     //CR = 4/6
      G_LoRaConfig.PowerCfig = 22;               //输入范围:-3~22,根据实际使用硬件LSD4RFC-2L722N10选择SX126xSetTxParams函数
      G_LoRaConfig.HeaderType = LORA_PACKET_EXPLICIT;       //包头格式设置,显性包头:LORA_PACKET_EXPLICIT;隐性包头:LORA_PACKET_IMPLICIT
      //若设置为显性包头,发送端将会将PalyLoad长度、编码率、CRC等加入到包头中发送给接收端
      G_LoRaConfig.CrcMode = LORA_CRC_ON;             //CRC校验开启:LORA_CRC_ON,关闭:LORA_CRC_OFF
      G_LoRaConfig.InvertIQ = LORA_IQ_NORMAL;         //IQ信号格式,LORA_IQ_NORMAL:标准模式,LORA_IQ_INVERTED:反转模式;
      G_LoRaConfig.PreambleLength = 8;      //前导码长度
      G_LoRaConfig.PayloadLength = 10;      //数据包长度
      if(SX126x_Lora_init() != NORMAL)  //无线模块初始化
      {
          while(1)
          {
               GPIO_ResetBits(GPIOC, GPIO_Pin_13);
          }
      }

  此时没有编译错误的话,就可以把模组插到板子上,看模组初始化是否能正常通过,SPI读写操作是否都正常了。

  七、分别在while(1)中加入LLCC68模组的发送和接收流程

  分别在两个板子上while(1)中加入LLCC68模组的发送和接收流程,编译然后继续修改BUG,直到编译没有错误为止。此处繁琐,请参考视频,和使用者实际的MCU情况进行操作。

  发送流程如下
          G_LoRaConfig.PayloadLength = 15;
          SX126X_TxPacket(TXbuffer);//发送数据
          while(1)
          {
              if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4) == 1)//未按下
              {
                  uint16_t flag;
                  flag = SX126xGetIrqStatus();
                  SX126xClearIrqStatus( IRQ_RADIO_ALL ); //clear flags
                  if((flag & IRQ_TX_DONE) == IRQ_TX_DONE)//
                  {
                      Delay(1);
                      break;
                  }
              }
              else
              {
                     Delay(1);
              }
          }
          Delay(400000);

  接收流程如下:
          SX126X_StartRx();
          while(1)
          {
              Delay(1);//R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_03_PIN_01, &p_port_value_portDIO1);
              if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4) == 1)
              {
                  uint16_t flag;
                  flag = SX126xGetIrqStatus();
                  SX126xClearIrqStatus( IRQ_RADIO_ALL ); //clear flags
                  if((flag & (IRQ_RX_DONE | IRQ_CRC_ERROR)) == IRQ_RX_DONE)//
                  {
                      Delay(1);//R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_04, BSP_IO_LEVEL_HIGH);
                      SX126X_RxPacket(RXbuffer);//
                      Delay(1);//R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_04, BSP_IO_LEVEL_LOW);
                      break;
                  }
                  else
                  {
                          //SX126X_StartRx();
                         Delay(1);
                          break;
                  }
              }
          }

  两个板子分别加入发送和接收流程并编译无错误后,再验证下接收板子能否正常接收到发送板子发送的数据,整个基础移植过程就完毕了。

  验证过模组的初始化,SPI读写,发送和接收在这种普通IO加while(1)方式下都好使后,就可以再把定时器,中断,和硬件SPI等加进去了。

  结论

  这种普通IO加while(1)方式未必是使用者使用模组的最终方式,但是这种最基础普通的移植方式确可以帮助使用者做好模组最基本的调试,能清楚确认好模组是否一切功能都正常。

  配套视频比较大,放在百度网盘上供读者查看,连接如下:

  链接:https://pan.baidu.com/s/1pIH-12rDFc4Z_BUHFRr6Tg
  提取码:wsnd

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表