MM32F5270

1►简介

在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用 CPU 的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。

FreeRTOS是一个RTOS类的嵌入式实时操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。

MM32F5270是一款搭载了安谋科技 Arm® Cortex-M33/STAR-MC1内核的MCU产品,其工作频率可达 120MHz,具有256KB Flash和192KB RAM,内置单精度浮点运算单元(Floating Point Unit,FPU),并支持数字信号处理(Digital Signal Processing, DSP)扩展,提供实时处理和高级中断处理能力,实现性能和电源效率的平衡,适合用于各种类型的实时控制应用。

本系列微课堂基于MM32F5270 MCU初步学习使用FreeRTOS开发,不足之处欢迎指出。

参考资料:

FreeRTOS官网:www.freertos.org

灵动微电子官网:www.mm32mcu.com

访问https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_performance/mm32f5270/  获取MM32F5270更多信息。

2►FreeRTOS移植

2.1 源码下载

点击官网标题下的Download可以去下载FreeRTOS源码,当前版本是V10.5.1(task.h文件中有注明版本)。

1.png

内容如下:

1718876349133112.png

包括3个文件夹、4个HTML格式的网页和2个.txt文档,重点在于上面的FreeRTOS 和FreeRTOS-Plus两个文件夹,其中FreeRTOS文件夹的内容就是FreeRTOS内核源码,而Plus版本还包括内核以外的组件和第三方补充,对于FreeRTOS移植,我们只需要用到FreeRTOS内核源码就行了。

FreeRTOS文件夹内容:

1718876355205437.png

Demo文件夹里面是 FreeRTOS的例程;

License文件夹里面是相关的许可信息;

Source文件夹是FreeRTOS源码;

Test文件夹是FreeRTOS的相关测试。

Source文件夹内容:

1718876364703165.png

其中.c文件就是FreeRTOS的源码文件,include文件夹是源码包含的一些头文件,portable文件夹是FreeRTOS操作系统和具体硬件的连接层。

portable文件夹内容:

1718876372677201.png

不同MCU内核及编译环境对应的portable文件有所差异,MemMang文件跟内存管理相关。

2.2 移植

移植 FreeRTOS首先需要一个基础工程,可以参考MM32F5270 LibSamples创建工程,或直接在任一例程基础上改动。

1) 向工程中添加FreeRTOS源码

在工程中新建一个名为FreeRTOS的文件夹:

1718876394795672.png

将FreeRTOS相关文件复制到此文件夹下:

1718876402545057.png

portable文件夹只留下GCC和MemMang两个文件夹,其他的都可以删除。

1718876408282878.png

2) 向工程分组中添加文件

打开基础工程,新建分组 FreeRTOS_CORE 和 FreeRTOS_PORTABLE,然后向这两个分组中添加文件:

1718876418868812.png

port.c文件位于portable\GCC\ARM_CM33_NTZ\non_secure文件夹。

heap_4.c文件位于portable\MemMang文件夹,提供RTOS内核所需的内存分配。

3) 添加头文件路径

添加FreeRTOS源码的头文件路径:

1718876429251848.png

4) 加入FreeRTOSConfig.h文件

FreeRTOSConfig.h是FreeRTOS的配置文件,通过宏定义来完成对系统的配置和裁剪。可以自己创建(参考 http://www.freertos.org/a00110.html),也可以从FreeRTOS的官方移植工程中复制。这里复制FreeRTOS\Demo\CORTEX_MPU_M33F_Simulator_Keil_GCC\Config\FreeRTOSConfig.h。

5) 修改重复定义的函数

FreeRTOS在port.c中定义了SysTick_Handler()、SVC_Handler()和PendSV_Handler()这三个函数,需要将工程mm32f5270_it.c中定义的三个同名函数注释掉。

6) 编译和修改

编译后如果一些报错或警告,根据提示修改即可,一般和Contex-M33的特定配置选项有关,如configENABLE_MPU、configENABLE_FPU、configENABLE_TRUSTZONE 根据实际应用情况来适配。

另外还要在FreeRTOSConfig.h修改如下宏定义:

设置configCPU_CLOCK_HZ为120000000,和当前MCU配置的系统时钟频率一致。

设置configTICK_RATE_HZ为1000,即FreeRTOS时钟节拍周期是1ms。

1718876447666018.png

3►验证

开发板使用Mini-F5277-OB,编写简单的FreeRTOS应用代码,测试FreeRTOS的移植是否成功。设计四个任务:start_task()、led1_task ()、led2_task ()和 float_task(),功能如下:

  • start_task():用来创建其他三个任务。

  • led1_task ():控制 LED1 的闪烁,提示系统正在运行。

  • led2_task ():控制 LED2 的闪烁。

  • float_task():浮点测试任务,用于测试FPU是否工作正常。

代码如下:

void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();

    xTaskCreate((TaskFunction_t)led1_task,
                (const char*) "led1_task", 
                (uint16_t)LED1_STK_SIZE,
                (void *)NULL,
                (UBaseType_t)LED1_TASK_PRIO,
                (TaskHandle_t*)&LED1Task_Handler);

    xTaskCreate((TaskFunction_t)led2_task,
                (const char*) "led2_task", 
                (uint16_t)LED2_STK_SIZE,
                (void *)NULL,
                (UBaseType_t)LED2_TASK_PRIO,
                (TaskHandle_t*)&LED2Task_Handler);

    xTaskCreate((TaskFunction_t)float_task,
                (const char*) "float_task", 
                (uint16_t)FLOAT_STK_SIZE,
                (void *)NULL,
                (UBaseType_t)FLOAT_TASK_PRIO,
                (TaskHandle_t*)&FLOATTask_Handler);

    vTaskDelete(StartTask_Handler);
    taskEXIT_CRITICAL();
}


void led1_task(void *p_arg)
{
    while(1)
    {
        PLATFORM_LED_Toggle(LED1);
        vTaskDelay(100);
    }
}


void led2_task(void *p_arg)
{
    while(1)
    {
        PLATFORM_LED_Toggle(LED2);
        vTaskDelay(500);
    }
}


void float_task(void *p_arg)
{
    static float float_num=0.00;
    while(1)
    {
        float_num+=0.01f;
        printf("float_num = %.4f\r\n", float_num);
        vTaskDelay(1000);
    }
}

主函数:

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

    PLATFORM_Init();

    xTaskCreate( (TaskFunction_t)start_task,
                 (const char*) "start_task", 
                 (uint16_t)START_STK_SIZE,
                 (void *)NULL,
                 (UBaseType_t)START_TASK_PRIO,
                 (TaskHandle_t*)&StartTask_Handler);

    vTaskStartScheduler();
}

程序执行情况:

板载LED1、LED2分别间隔100ms、500ms闪烁。

串口调试助手打印float_num的值不断增加,每次增加0.01,工程和KEIL已经设置使用FPU,调试观察会用到浮点寄存器s0、s2和浮点指令VLDR、VADD.F32。

与程序设置相符,FreeRTOS移植成功。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 21

LwIP简介

LwIP是轻量化的TCP/IP协议,由瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP具有高度可移植性、代码开源,提供了三种编程接口(API):RAW API、NETCONN API 和 Socket API,用于与TCP/IP代码进行通信。

通过官网(http://savannah.nongnu.org/projects/lwip/)可获取LwIP源码包及contrib包。源代码包主要包含LwIP内核的源码文件,contrib包中包含部分移植和应用LwIP的demo。contrib包不属于LwIP内核的一部分,但很有参考价值。

以lwip-2.1.2版本的源码包为例,如图1所示,该源码包分为三部分, src 文件为LWIP源代码文件, doc 文件包含LwIP相关文档, test 为LwIP测试文件,使用时主要关注于 src 文件下的内容。

LwIP内核是由一系列模块组合而成,包括 TCP/IP 协议栈的各种协议、内存管理、数据包管理、网卡接口、基础功能类模块、API等,构成这些模块的源文件就分布在api、apps、core、netif中,头文件则汇总在include中。

  • api

    NETCONN API和Socket API相关的源文件,只有在操作系统的环境中,才能被编译

  • apps

    应用程序的源文件,包括常见的应用程序,如httpd、mqtt、tftp、sntp、snmp等

  • core

    LwIP的内核源文件

  • include

    LwIP所有模块对应的头文件

  • netif

    与网卡移植有关的文件

1.png

图1 LwIP-2.1.2源码包

移植接口解析

LwIP使用数据结构体netif来描述网卡,并提供统一接口,需要与以太网底层驱动接口函数结合使用,例如底层驱动负责完成网卡的初始化、网卡的数据收发等,当LwIP内核需要发送一个数据包时,就会通过LWIP提供的接口函数去调用底层网卡的发送函数,将数据由硬件接口与软件内核衔接在一起。

contrib文件中包含部分可使用的网卡移植模板文件,其中ethernetif.c文件(contrib-2.1.0\examples\ethernetif目录下的ethernetif.c文件)为底层接口驱动的模板,以 LibSamples 为例,若要基于 LibSample的以太网驱动移植LwIP,则需参考ethernetif.c模板,根据以太网驱动及所需配置进行修改,将底层驱动 ethernet 相关函数填充到LwIP所需的指定功能函数中。

ethernetif.c文件中的函数通常为与硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,经过LwIP协议栈内部进行处理后,从应用层就能得到数据或者可以发送数据。该文件中包括函数:low_level_init()、low_level_output()、low_level_input()、ethernetif_input()和ethernetif_init()函数。

  • ethernetif_init()

    LwIP中默认的网卡初始化函数,内部封装了low_level_init()函数

  • ethernetif_input()

    该函数用于接收网卡数据,内部封装了low_level_input()函数,在接收完毕时,将数据通过pbuf递交给上层。

  • low_level_init()

    low_level_init()函数主要是根据实际情况对网卡进行一系列的初始化工作,例如:初始化MAC地址、长度, 设置最大传输包的大小,设置网卡的属性字段等功能。

    该函数中需要调用以太网底层驱动中的相关初始化函数,以 LibSamples为例,该函数需要调用以太网底层驱动 hal_enet.c/.h 的 PHY、MAC、DMA相关初始化函数并进行配置。

  • low_level_output()

    该函数用于实现网卡发送数据,是一个底层驱动函数,需根据以太网底层驱动进行相应修改,若想通过一个网卡发送数据,则需要将该数据传入LwIP内核中,经过层层封装最后存储在pbuf数据包中,需注意pbuf以链表的形式存在,数据发送时是以一整个数据包全部发送的。

  • low_level_input()

    low_level_input()函数用于从网卡中接收一个数据包,并将该数据包封装在pbuf中递交给上层,该函数需要调用以太网底层驱动中的接收函数。

移植LwIP协议栈

基于LibSamples的以太网驱动对LwIP进行移植,需先将LwIP源文件中的部分文件添加到LibSamples中,如: src 源文件、 include 头文件。

若想令LwIP运行,还需补充contrib文件中部分内容,如图2所示,由于部分源文件中使用头文件写法为”arch/xx”,因此,在src文件下新建arch文件,并将需要修改的模板文件及contrib中的部分接口文件放入arch文件中。

  • ethernetif.c网卡移植模板文件

  • cc.h文件主要完成协议栈内部使用的数据类型的定义

  • lwipopts.h文件包含了用户对协议栈内核参数进行的配置,若未在lwipopts.h文件中进行配置,则LwIP会使用opt.h中的默认参数

  • perf.h文件是实现与系通通计和测量相关的功能,若未使用该功能,则无需修改

  • bpstruct.h、epstruct.h由contrib文件下的ports文件所提供,属于堆栈的一部分,无需修改

2.png

图2 LWIP移植所需部分文件

lwipopts.h文件中需要根据是否为操作系统模拟层、堆内存大小、是否使用TCP及TCP相关配置等进行宏定义配置,例如:宏定义 NO_SYS 表示无操作系统模拟层,因为当前为无操作系统的移植,所以设置该宏定义为1。

 ...
/**
 * NO_SYS==1: Provides VERY minimal functionality. Otherwise,
 * use LwIPfacilities.
 */
#define NO_SYS                  1
...

cc.h文件中包含处理器相关的变量类型、数据结构及字节对齐的相关宏,需根据处理器及编译器进行修改。

...
#define LWIP_NO_STDINT_H  1

typedef unsigned   char    u8_t;
typedef signed     char    s8_t;
typedef unsigned   short   u16_t;
typedef signed     short   s16_t;
typedef unsigned   long    u32_t;
typedef signed     long    s32_t;
typedef u32_t mem_ptr_t;
typedef int sys_prot_t;

#define U16_F "hu"
#define S16_F "d"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
#define SZT_F "uz" 
...
#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
...

low_level_init移植接口实现

头文件配置并修改完成后,需要对移植模板文件 ethernetif.c 进行修改。

在以太网底层驱动与LwIP初始化接口的衔接上,对low_level_init()进行修改,在对LwIP的netif结构体进行相关配置之前,需要通过以太网底层驱动使硬件被初始化;初始化后,配置 MAC 硬件地址,链接发送描述符及接收描述符并进行描述符内容配置,配置描述符地址,配置完成后,使能以太网 DMA 启动传输,此时,初始化完成。

static void
low_level_init(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;

  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] = BOARD_MAC_ADDR0;
  netif->hwaddr[1] = BOARD_MAC_ADDR1;
  netif->hwaddr[2] = BOARD_MAC_ADDR2;
  netif->hwaddr[3] = BOARD_MAC_ADDR3;
  netif->hwaddr[4] = BOARD_MAC_ADDR4;
  netif->hwaddr[5] = BOARD_MAC_ADDR5;

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

#if LWIP_IPV6 && LWIP_IPV6_MLD
  /*
   * For hardware/netifs that implement MAC filtering.
   * All-nodes link-local is handled by default, so we must let the hardware know
   * to allow multicast packets in.
   * Should set mld_mac_filter previously. */
  if (netif->mld_mac_filter != NULL) {
    ip6_addr_t ip6_allnodes_ll;
    ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
    netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
  }
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */

    ETH_GPIOInit();
    SysTick->CTRL |= ((uint32_t)0x00000004);
    SysTick_Config(120000000 / 1000);

    ETH_InitTypeDef ptr;
    ETH_StructInit(&ptr);
    ptr.ETH_AutoNegotiation = ETH_AutoNegotiation_Disable;
    ETH_Init(&ptr, ENET_PHY_ADDR);
    ETH->DMAOMR &= ~ETH_DMAOMR_OSF;

    /* Enable ETH DMA interrupt. */
    ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R, ENABLE);

    NVIC_InitTypeDef        NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = ENET_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    /* Config macd filter address. */
    ENET_SetupMacAddrFilter(0x1u<<31|0x1u<<5, ENET_ADDR_FILTER_NUM, 0u, netif->hwaddr);

    /* Set tx dma desp link. */
    memset(enet_txdma_desp_tbl, 0, sizeof(enet_txdma_desp_tbl));
    for (uint32_t i = 0u; i < ENET_TX_NUM - 1; i++) {
        enet_txdma_desp_tbl[0].CS |= TXDMA_DES0_TCH; /* TCH  = 1u. */
        enet_txdma_desp_tbl[0].BUF1ADDR = (uint32_t)(enet_txbuf[i]);
        enet_txdma_desp_tbl[0].BUF2NDADDR = (uint32_t)(&enet_txdma_desp_tbl[i + 1]);
    }
    enet_txdma_desp_tbl[0].CS |= TXDMA_DES0_TCH; /* TCH  = 1u. */
    enet_txdma_desp_tbl[0].BUF1ADDR = (uint32_t)(enet_txbuf[ENET_TX_NUM - 1]);
    enet_txdma_desp_tbl[0].BUF2NDADDR = (uint32_t)(&enet_txdma_desp_tbl[0]);

    /* Set enet tx dma descriptor first address. */
    ETH->DMATXDSAR = (uint32_t)(&enet_txdma_desp_tbl[0]);
    enet_usable_txdma_desp = &enet_txdma_desp_tbl[0];

    /* Set rx dma desp link. */
    memset(enet_rxdma_desp_tbl, 0, sizeof(enet_rxdma_desp_tbl));
    for (uint32_t i = 0; i < ENET_RX_NUM - 1; i++) {
        enet_rxdma_desp_tbl[i].CS |= RXDMA_DES0_OWN; /* RDES0[OWN]  = 1. */
        enet_rxdma_desp_tbl[i].BL |= RXDMA_DES1_RCH; /* RDES1[RCH] = 1. */
        enet_rxdma_desp_tbl[i].BL &= ~ RXDMA_DES1_RBS1;
        enet_rxdma_desp_tbl[i].BL |= ENET_RX_BUFLEN; /* RDES1[RBS1] = ENET_RX_BUFLEN. */
        enet_rxdma_desp_tbl[i].BUF1ADDR = (uint32_t)enet_rxbuf[i];
        enet_rxdma_desp_tbl[i].BUF2NDADDR = (uint32_t)(&enet_rxdma_desp_tbl[i+1]);
    }
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].CS |= RXDMA_DES0_OWN; /* RDES0[OWN]  = 1. */
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL |= RXDMA_DES1_RCH; /* RDES1[RCH] = 1. */
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL &= ~ RXDMA_DES1_RBS1;
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BL |= ENET_RX_BUFLEN; /* RDES1[RBS1] = ENET_RX_BUFLEN. */
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BUF1ADDR = (uint32_t)enet_rxbuf[ENET_RX_NUM - 1];
    enet_rxdma_desp_tbl[ENET_RX_NUM - 1].BUF2NDADDR = (uint32_t)(&enet_rxdma_desp_tbl[0]);

    ETH->DMARXDSAR = (uint32_t)enet_rxdma_desp_tbl;
    enet_first_rxdma_desp = &enet_rxdma_desp_tbl[0];

    ETH_Start();
}

low_level_output移植接口实现

low_level_output()函数与以太网底层驱动的发送功能函数相结合,将LwIP要发送的数据存储到以太网发送描述符中所指定的存储区域中,再对发送描述符进行配置并进行发送。

static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *q;

  /* Get current destination address. */
  ETH_DMADESCTypeDef * txdma_desp = enet_usable_txdma_desp;

  if (0u != (txdma_desp->CS & TXDMA_DES0_OWN) ){
    return ERR_USE;
  }

#if ETH_PAD_SIZE
  pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif

  uint32_t e_offset = 0; /* record enet module buf offset. */
  for (q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
    for (uint32_t i = 0; i < q->len; i++) {
      ((uint8_t*)(txdma_desp->BUF1ADDR))[e_offset] = ((uint8_t*)(q->payload))[i];
      e_offset++;
      if (e_offset == ENET_TX_BUFLEN) {
        txdma_desp = (ETH_DMADESCTypeDef*)(txdma_desp->BUF2NDADDR);
        if ((txdma_desp->CS & TXDMA_DES0_OWN) != 0u) {
          return ERR_USE;
        }
        e_offset = 0;
      }
    }
  }

  if (p->tot_len <= ENET_TX_BUFLEN) {
    enet_usable_txdma_desp->CS |= TXDMA_DES0_TFS | TXDMA_DES0_TLS | TXDMA_DES0_OWN;
    enet_usable_txdma_desp->BL &= ~0x1FFF;
    enet_usable_txdma_desp->BL |= p->tot_len; /* TBS1!< Transfer buffer size 1. */
    enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR;
  } else {
    enet_usable_txdma_desp->CS |= TXDMA_DES0_TFS; /* TFS = 1u. */
    enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TLS; /* TLS = 0u. */
    enet_usable_txdma_desp->BL &= ~0x1FFF;
    enet_usable_txdma_desp->BL |= ENET_TX_BUFLEN; /*!< Transfer buffer size 1. */
    enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR;
    for (uint32_t i = ENET_TX_BUFLEN; i < p->tot_len - ENET_TX_BUFLEN; i+= ENET_TX_BUFLEN) {
      enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TFS; /* TFS = 0u. */
      enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TLS; /* TLS = 0u. */
      enet_usable_txdma_desp->BL &= ~0x1FFF;
      enet_usable_txdma_desp->BL |= ENET_TX_BUFLEN;
      enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR;
    }
    enet_usable_txdma_desp = (ETH_DMADESCTypeDef*)enet_usable_txdma_desp->BUF2NDADDR;
    enet_usable_txdma_desp->CS &= ~TXDMA_DES0_TFS; /* TFS = 0u. */
    enet_usable_txdma_desp->CS |= TXDMA_DES0_TLS;  /* TLS = 1u. */
    enet_usable_txdma_desp->BL &= ~0x1FFF;
    enet_usable_txdma_desp->BL |= (p->tot_len % ENET_TX_BUFLEN);
  }

  if (0 != (ETH->DMASR &ETH_DMA_TransmitProcess_Suspended)){
    ETH_ResumeDMATransmission();
  }

  MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);
  if (((u8_t *)p->payload)[0] & 1) {
    /* broadcast or multicast packet*/
    MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);
  } else {
    /* unicast packet */
    MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
  }
  /* increase ifoutdiscards or ifouterrors on error */

#if ETH_PAD_SIZE
  pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

  LINK_STATS_INC(link.xmit);

  return ERR_OK;
}

low_level_input移植接口实现

low_level_input()函数与以太网底层驱动的接收功能函数相结合,将接收到的数据存入LwIP的pbuf链中。ethernetif_input()函数调用low_level_input()函数。

static struct pbuf *
low_level_input(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *p, *q;
  u16_t len;

  ETH_DMADESCTypeDef * rxdma_desp = enet_first_rxdma_desp;
  for (uint32_t i = 0; i < ENET_RX_NUM; i++) {
    if ((rxdma_desp->CS & RXDMA_DES0_RLS) != 0) {
        len = (uint32_t)(rxdma_desp->CS & RXDMA_DES0_FL)>>16;
        break;
    } else if ((rxdma_desp->CS & RXDMA_DES0_OWN) != 0) {
      return NULL;
    } else {
      rxdma_desp = (ETH_DMADESCTypeDef*)(rxdma_desp->BUF2NDADDR);
    }
  }

#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif

  /* We allocate a pbuf chain of pbufs from the pool. */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

  if (p != NULL) {

#if ETH_PAD_SIZE
    pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif

    /* We iterate over the pbuf chain until we have read the entire
     * packet into the pbuf. */
    uint32_t e_offset = 0;
    rxdma_desp = enet_first_rxdma_desp;
    for (q = p; q != NULL; q = q->next) {
      /* Read enough bytes to fill this pbuf in the chain. The
       * available data in the pbuf is given by the q->len
       * variable.
       * This does not necessarily have to be a memcpy, you can also preallocate
       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
       * actually received size. In this case, ensure the tot_len member of the
       * pbuf is the sum of the chained pbuf len members.
       */
      for (uint32_t i = 0; i < q->len; i++) {
        ((uint8_t*)q->payload)[i] = ((uint8_t*)rxdma_desp->BUF1ADDR)[e_offset];
        e_offset++;
        if (e_offset == ENET_RX_BUFLEN) {
          rxdma_desp = (ETH_DMADESCTypeDef*)(rxdma_desp->BUF2NDADDR);
          e_offset = 0;
        }
      }
    }

    MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
    if (((u8_t *)p->payload)[0] & 1) {
      /* broadcast or multicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
    } else {
      /* unicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
    }
#if ETH_PAD_SIZE
    pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

    LINK_STATS_INC(link.recv);
  } else {
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
    MIB2_STATS_NETIF_INC(netif, ifindiscards);
  }

  do {
    enet_first_rxdma_desp->CS |= RXDMA_DES0_OWN; /* Set OWN bit. */
    enet_first_rxdma_desp = (ETH_DMADESCTypeDef*)enet_first_rxdma_desp->BUF2NDADDR;
  } while ((enet_first_rxdma_desp->CS&RXDMA_DES0_OWN) == 0);

  if (RESET != (ETH_GetDMAFlagStatus((0x4 << 17)) ) ){ /*!< ENET dma rx fifo not active, need to be weak up. */
      ETH_ResumeDMAReception(); /* Wakeup enet dma receive. */
  }

  return p;
}

ENET_IRQHandler中断服务函数实现

/* ENET IRQHandler. */
void ENET_IRQHandler()
{
  if (0 != ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R))
  {
      ethernetif_input(gnetif);
      ETH_DMAClearFlag(ETH_DMA_FLAG_R);
  }
}

自定义参数声明及函数实现

需要根据实际选用的开发板和运行参数等进行宏定义配置,如 IP 地址、端口号、MAC地址需要根据实际的网络环境进行配置,这里以LwIP_TCP_Client样例为例,将这些参数定义到了 lwip_tcp_client.h 文件中。

/* initialization enet. */
#define ENET_PHY_ADDR                       0x01      /* Select PHY address. */
#define ENET_PHY_CONTROLREG                 0u        /* PHY control register address. */
#define ENET_PHY_STATUSREG                  1u        /* PHY status register sddress. */
#define ENET_PHY_RESET                      0x8000    /* Set PHY reset, use in ENET_PHY_CR registers */
#define ENET_PHY_SPEED100M                  0x2000    /* Set PHY speed. */
#define ENET_PHY_FULLDUPLEX                 0x0100    /* Set PHY duplex mode about full duplex. */
#define ENET_PHY_LINK                       0x0004    /* PHY link-up. */
#define ENET_PHY_UNIDIRECTIONAL             0x0080    /* PHY has the ability to encode and transmit data from PHY through MII interface, regardless of whether PHY has determined that an effective link has been connected and established. */
#define ENET_PHY_AUTONEGOTIATION            0x1000    /* PHY auto negotiation. */

#define ENET_TX_BUFLEN                      1500u     /* Tx buffer length. */
#define ENET_TX_NUM                         4u        /* The number of tx. */
#define ENET_RX_BUFLEN                      1500u     /* Configure the frame length of a received frame. */
#define ENET_RX_NUM                         4u        /* The configured number of received descriptor that can be used for receiving. */
#define ENET_ADDR_FILTER_NUM                5u         /* Select MAC address filter number from 0~5. */


#define BOARD_MAC_ADDR0                     2u
#define BOARD_MAC_ADDR1                     1u
#define BOARD_MAC_ADDR2                     0u
#define BOARD_MAC_ADDR3                     0u
#define BOARD_MAC_ADDR4                     0u
#define BOARD_MAC_ADDR5                     0u

#define BOARD_IP_ADDR0                      192u
#define BOARD_IP_ADDR1                      168u
#define BOARD_IP_ADDR2                      105u
#define BOARD_IP_ADDR3                      98u

#define BOARD_NETMASK_ADDR0                 255u
#define BOARD_NETMASK_ADDR1                 255u
#define BOARD_NETMASK_ADDR2                 255u
#define BOARD_NETMASK_ADDR3                 0u

#define BOARD_GW_ADDR0                      192u
#define BOARD_GW_ADDR1                      168u
#define BOARD_GW_ADDR2                      1u
#define BOARD_GW_ADDR3                      1u

#define BOARD_TCP_SERVER_IPADDR0            192u
#define BOARD_TCP_SERVER_IPADDR1            168u
#define BOARD_TCP_SERVER_IPADDR2            105u
#define BOARD_TCP_SERVER_IPADDR3            85u

#define BOARD_TCP_SERVER_PORT               6800u

#define TXDMA_DES0_TCH                      0x01u<<20
#define TXDMA_DES0_TFS                      0x01u<<28
#define TXDMA_DES0_TLS                      0x01u<<29
#define TXDMA_DES0_OWN                      0x01u<<31

#define RXDMA_DES0_RLS                      0x01u<<8
#define RXDMA_DES0_FL                       0x3FFFu<<16
#define RXDMA_DES0_OWN                      0x01u<<31
#define RXDMA_DES1_RCH                      0x01u<<14
#define RXDMA_DES1_RBS1                     0x1FFFu

#define FILTERS_RECEIVE_ALL                 0x01u<<31
#define FILTERS_BOARDCAST_FILTER            0x01u<<5

在lwip_tcp_client.c文件中除了对Ethernet相关的时钟引脚进行配置及使用到的系统时钟对应参数申明外,也根据LwIP协议栈实际的应用需求,实现了关于MAC地址过滤器的函数。

void ENET_SetupMacAddrFilter(uint32_t filter, uint32_t addr_id, uint32_t addr_mask, uint8_t * addr)
{
    ETH->MACAFR |= filter;

    if ( (0u != (filter & ETH_SourceAddrFilter_Normal_Enable)) || (0u != (filter & 0x100)) ) /* Set source address filter. */
    {
        ETH->MACA0HR = ( 0x1u<<31 | 0x1u<<30 | (uint32_t)addr[4u] | ((uint32_t)addr[5u]<<8u) );;
        ETH->MACA0LR = ( (uint32_t)addr[0u] | ((uint32_t)addr[1u] << 8u) | ((uint32_t)addr[2u] << 16u) | ((uint32_t)addr[3u] << 24u) );;
    }
    else if ( (0u != (filter & 0x10)) || (0u != (filter & 0x100))  ) /* Set destination address filter. */
    {
        ETH->MACAFR &= ~(0x1u<<4 | 0x1u<<1);
    }

    if (0u != addr_mask)
    {
        ETH->MACA0HR|= addr_mask;
    }
}


/* Returns the current time in milliseconds, this API from lwip/sys.h */
uint32_t sys_now(void)
{
    return systime_ms;
}

uint32_t sys_jiffies(void)
{
  return systime_ms * 1000000;
}

样例说明

基于移植的 LwIP协议,LibSamples还提供了展示 TCP 协议客户端与服务器通信的 lwip_tcp_client、lwip_tcp_server样例,展示 UDP 协议客户端与服务器通信的 lwip_udp_client、lwip_udp_server。

样例实现环境搭建

  • 本文基于搭载了MM32F5277E9P MCU的开发板 PLUS-F5270 V2.0进行实现,使用2根网线,分别连接电脑与路由器、开发板与路由器。

  • 在官网(http://free.cmsoft.cn/reslink.php?id=205)下载网络调试助手NetAssist并安装,用于后续的样例功能验证。

  • 打开电脑终端(WIN+R键,输入CMD),然后输入指令 ipconfig/all ,查看本机的以太网IP地址为 192.168.108.85 ;

  • 在终端中输入命令 netstat -na 获取本地开放端口,这里我们获取到可用端口号为 49153  。

LwIP_TCP_Client

LwIP_TCP_Client 样例用于展示基于以太网及 LwIP使用 TCP 协议作为客户端,进行客户端与服务器之间的通信。

若想使用LwIP,则需要先将协议栈初始化,并设置主机的IP地址、子网掩码、网关地址等。需注意,样例工程中所设置的IP地址需要与路由器处于同一子网,如图3所示,在命令提示符(CMD)中使用命令 ipconfig/all 可查看各IP的详细信息,例如所查出的以太网IPx4地址为192.168.108.85,则在样例工程中可设置IP地址为192.168.108.98,网关地址与子网掩码的配置需与所查出的以太网默认网关及子网掩码相同。

void app_lwip_init(void)
{
    ip4_addr_t ipaddr;
    ip4_addr_t netmask;
    ip4_addr_t gw;
    IP4_ADDR(&ipaddr, BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3);
    IP4_ADDR(&netmask, BOARD_NETMASK_ADDR0, BOARD_NETMASK_ADDR1, BOARD_NETMASK_ADDR2, BOARD_NETMASK_ADDR3);
    IP4_ADDR(&gw, BOARD_GW_ADDR0, BOARD_GW_ADDR1, BOARD_GW_ADDR2, BOARD_GW_ADDR3);
    lwip_init();
    ...
}

3.png

图3 在CMD界面通过命令查询以太网IP信息

在配置完IP地址等必要信息后,需挂载网卡,在LwIP中网卡挂载函数为 netif_add() 函数,将所配置的数据传入该函数中。

void app_lwip_init(void)
{
    ...
    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

    netif_set_default(&gnetif);

    if (netif_is_link_up(&gnetif))
    {
        netif_set_up(&gnetif);
    }
    else
    {
        netif_set_down(&gnetif);
    }
}


LwIP协议栈初始化后,需要对所使用的 TCP Client(TCP客户端)进行初始化配置。在 LwIP中存在多个与 TCP 相关的函数,LwIP_TCP_Client样例所使用到的函数包括:

  • tcp_new()

    创建一个TCP的PCB控制块

  • tcp_connect()

    连接远端主机

  • tcp_err()

    控制块err字段注册的回调函数,遇到错误时被调用

  • tcp_write()

    构造一个报文并放入控制块的发送缓冲队列中

  • tcp_recv()

    控制块rev字段注册的回调函数,当接收到新数据是被调用

  • tcp_recved()

    当程序处理完数据后调用该函数,通知内核更新接收窗口

  • tcp_close()

    关闭一个TCP连接

TCP 客户端的工作流程包括:新建控制块、建立连接、发送请求与接收数据并处理。TCP客户端工作流程如图4所示。

4.png

图4 TCP客户端流程图

TCP Client(TCP客户端)进行初始化配置时,通过 IP4_ADDR() 函数将目标服务器的IP写入结构体;再通过 tcp_new() 函数为TCP客户端分配一个结构,当该结构不为空时,使用 tcp_connect() 函数与目标服务器进行连接,该函数中配置目标端口和目标IP参数并调用连接完成回调函数。

void app_tcp_client_init(void)
{
    struct tcp_pcb *tcp_client_pcb;
    ip_addr_t app_server_ip;
    /* Write the IP of the target server into a structure, which is the local connection IP address of the pc. */
    IP4_ADDR(&app_server_ip, BOARD_TCP_SERVER_IPADDR0, BOARD_TCP_SERVER_IPADDR1, BOARD_TCP_SERVER_IPADDR2, BOARD_TCP_SERVER_IPADDR3);
    /* Assign a structure to the TCP client */
    tcp_client_pcb = tcp_new();

    if (tcp_client_pcb != NULL)
    {
        /* Connect with the target server, and the parameters include the target port and the target IP. */
        tcp_connect(tcp_client_pcb, &app_server_ip, BOARD_TCP_SERVER_PORT, app_tcp_client_connected);
        /* Registered connection error handling callback function. */
        tcp_err(tcp_client_pcb, app_tcp_client_connecterror);
    }
}

在连接完成回调函数中,使用 tcp_write() 函数发送问候字符串以建立连接,并使用 tcp_recv() 函数配置接收回调函数。

    static err_t app_tcp_client_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{
    /* Send a greeting string to establish a connection */
    tcp_write(pcb, clientstring, strlen(clientstring), 1u);
    /* Configure the receive callback function */
    tcp_recv(pcb, app_tcp_client_xfer);

    return ERR_OK;
}

在TCP客户端接收数据后的数据处理回调函数中,接收到有效数据后,通过tcp_recved()更新接收窗口,使用 tcp_write() 函数将接收到的服务器内容回显。

static err_t app_tcp_client_xfer(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)
{
    if (tcp_recv_pbuf != NULL)
    {
        /* Update the receiving window */
        tcp_recved(pcb, tcp_recv_pbuf->tot_len);
        tcp_write(pcb, tcp_recv_pbuf->payload, tcp_recv_pbuf->len, 1u);

        pbuf_free(tcp_recv_pbuf);
    }
    else if (err == ERR_OK)
    {
        tcp_close(pcb);
        app_tcp_client_init();
        return ERR_OK;
    }
    return ERR_OK;
}

lwip_tcp_client 样例的实验现象如图5所示,通过网络调试助手可查看到连接成功后,远端服务器收到客户端发送的数据,服务器向客户端发送任意数据包后,客户端回显相同数据。

5.png

图5 lwip_tcp_client样例实验现象

注意事项

  • 在官网(http://free.cmsoft.cn/reslink.php?id=205)下载网络调试助手NetAssist并安装,用于后续的样例功能验证。

  • 打开电脑终端(WIN+R键,输入CMD),然后输入指令` ipconfig/all `,查看本机的以太网IP地址。

  • 在配置 IP 地址和端口号时,当连接了WIFI后需要注意我们选用的是以太网的IP地址,而非WLAN的IP地址。

  • 在终端中输入命令 `netstat -na` 获取本地开放端口。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

1►LIN总线简介

LIN(Local Interconnect Network)总线是基于UART/SCI(通用异步收发器/串行接口)的低成本串行通讯协议,其目标定位于车身网络模块节点间的低端通信,主要用于智能传感器和执行器的串行通信。LIN总线采用单主多从的组网方式,没有CAN总线那样的仲裁机制,辅以简单驱动程序便可实现LIN协议。LIN节点由控制芯片和LIN收发器构成,一般通过芯片搭载的UART模块来实现,主节点控制传输时刻,控制整个网络的通信,从节点按照主节点的调度进行通信。

2►LIN报文结构

LIN总线上有“显性”和“隐性”两种互补的逻辑电平。显性电平是逻辑 0,隐性电平是逻辑1,总线上实行“线与”。

一帧LIN报文由帧头(Header)和应答(Response)两部分组成。主机任务负责发送帧头,从机任务接收帧头并对帧头所包含信息进行解析,然后决定是发送应答,还是接收应答,还是不作任何反应。帧在总线上的传输如下图所示:

1.png

帧头包括同步间隔段、同步段以及受保护ID段(PID)。应答包括数据段和校验和段。LIN报文帧整体结构如下图所示。

2.png

同步间隔段

同步间隔段标志一帧的开始,由同步间隔(Break)和间隔符(Break Delimiter)构成。同步间隔段至少有13个显性位,间隔符至少有一个隐形位。

同步段

同步段固定一个字节,值固定为0x55。

在LIN帧中,除了同步间隔段,后面各段都是通过字节域的格式传输的。LIN的字节域就是指标准的UART数据传输格式,字节域包括1位起始位(显性)+8位数据位+1位停止位(隐性)。数据传输都是先发送LSB,最后发送 MSB。LIN总线将下降沿作为判断标志,通过字节0x55(01010101b)进行同步,从机节点上可以采用非高精度时钟,如果存在偏差,可以通过同步场来调整,使从机节点数据的波特率与主机节点一致。

受保护ID段

受保护ID段由6位帧ID和2位奇偶校验位组成,帧ID范围为0x00~0x3F共64个。

帧ID标识了帧的类别,从机任务根据帧头ID作出反应(接收/发送/忽略应答),其中P0与P1效验如下:

P0 = ID0⊕ID1⊕ID2⊕ID4

P1 = ¬(ID1⊕ID3⊕ID4⊕ID5)

其中“⊕”代表“异或”运算,“¬”代表“取非”运算。

由公式可以看出,PID 不会出现全 0 或全 1 的情况,如果从机节点收到了“0xFF”或“0x00”,可判断传输错误。LIN总线根据帧ID的不同,将报文分为信号携带帧、诊断帧、保留帧。

3.png

应注意从机应答帧是一个完整的帧,与帧结构中的“应答”不同。

数据段

数据段包含1~8个字节,可以分为两种数据类型:信号和诊断消息。信号由信号携带帧传递,诊断消息由诊断帧传递。LIN协议规定可传输的LIN字节数为2、4、8,并不是1~8内任意一个数字。一般应用方面会统一字节数,通常是每帧传输8个字节。

校验和段

校验和段是为了对帧传输内容进行效验。效验分为标准型校验与增强型校验。

将校验对象的各字节作带进位二进制加法(当结果大于等于256 时就减去255),并将所得最终的和逐位取反,以该结果作为要发送的校验和。接收方根据校验和类型,对接收数据作相同的带进位二进制加法,最终的和不取反,并将该和与接收到的校验和作加法,如果结果为0xFF,则校验和无误。这在一定程度上保证了数据传输的正确性。

采用标准型还是增强型是由主机节点管理,发布节点和收听节点根据帧ID来判断采用哪种校验和。

4.png

3►LIN通信实验

MM32F5270的UART支持LIN协议下收发断开符号,通过配置UART,根据总线特征编写LIN驱动程序,实现LIN总线通信。相关代码参考灵动官网的LibSamples或在此基础上修改。

3.1 LIN驱动程序

同步间隔段

配置UART支持LIN协议下收发断开符号:

void LIN_MASTER_Break(void)
{
    LIN_MASTER_TXBRK_InterruptFlag = 0;

    UART_LINCmd(UART1, ENABLE);
    UART_SendBreak(UART1);

    while (0 == LIN_MASTER_TXBRK_InterruptFlag)
    {
    }
}

同步段

主机发送0x55:

void LIN_MASTER_SyncByte(void)
{
    LIN_MASTER_SendData(0x55);
}

受保护ID段

uint8_t LIN_FrameIDToPID(uint8_t FrameID)
{
    uint8_t i  = 0;
    uint8_t P0 = 0, P1 = 0, PID = 0xFF;
    uint8_t ID_BIT[6] =
    {
        0, 0, 0, 0, 0, 0
    };

    if (FrameID < 0x40)
    {
        PID = FrameID;

        for (i = 0; i < 6; i++)
        {
            if (FrameID & (0x01 << i))
            {
                ID_BIT[i] = 1;
            }
            else
            {
                ID_BIT[i] = 0;
            }
        }

        P0 =  (ID_BIT[0] ^ ID_BIT[1] ^ ID_BIT[2] ^ ID_BIT[4]) & 0x01;
        P1 = ~(ID_BIT[1] ^ ID_BIT[3] ^ ID_BIT[4] ^ ID_BIT[5]) & 0x01;

        if (P0)
        {
            PID |= 0x40;
        }

        if (P1)
        {
            PID |= 0x80;
        }
    }

    return (PID);
}

数据段

主机发送数据:

void LIN_MASTER_SendData(uint8_t Data)
{
    UART_SendData(UART1, Data);

    while (RESET == UART_GetFlagStatus(UART1, UART_FLAG_TXC))
    {
    }
}

从机发送数据:

void LIN_SLAVE_SendData(uint8_t Data)
{
    UART_SendData(UART1, Data);

    while (RESET == UART_GetFlagStatus(UART1, UART_FLAG_TXC))
    {
    }
}

校验和段

标准型校验:

uint8_t LIN_ClassicChecksum(uint8_t *Buffer, uint8_t Length)
{
    uint8_t  i = 0;
    uint16_t Checksum = 0;

    for (i = 0; i < Length; i++)
    {
        Checksum += Buffer[i];

        if (Checksum > 0xFF)
        {
            Checksum %= 0xFF;
        }
    }

    return (~(uint8_t)(Checksum & 0x00FF));
}

增强型校验:

uint8_t LIN_EnhancedChecksum(uint8_t PID, uint8_t *Buffer, uint8_t Length)
{
    uint8_t  i = 0;
    uint16_t Checksum = PID;

    for (i = 0; i < Length; i++)
    {
        Checksum += Buffer[i];

        if (Checksum > 0xFF)
        {
            Checksum %= 0xFF;
        }
    }

    return (~(uint8_t)(Checksum & 0x00FF));
}

主机发送帧头

void LIN_MASTER_SendHeader(uint8_t PID)
{
    LIN_MASTER_Break();

    LIN_MASTER_SyncByte();

    LIN_MASTER_SendData(PID);
}

主机发送报文

诊断帧ID包括主机请求帧0x3C、从机应答帧0x3D,诊断帧用标准型校验和,其他帧使用增强型校验和。

void LIN_Master_SendFrame(uint8_t FrameID, uint8_t *Buffer, uint8_t Length)
{
    uint8_t i = 0;
    uint8_t Checksum = 0;
    uint8_t PID = LIN_FrameIDToPID(FrameID);

    if ((0x3C == FrameID) || (0x3D == FrameID))
    {
        Checksum = LIN_ClassicChecksum(Buffer, Length);
    }
    else
    {
        Checksum = LIN_EnhancedChecksum(PID, Buffer, Length);
    }

    LIN_MASTER_SendHeader(PID);

    for (i = 0; i < Length; i++)
    {
        LIN_MASTER_SendData(Buffer[i]);
    }

    LIN_MASTER_SendData(Checksum);
}

从机发布数据

从机解析帧头信息,将主机发送的PID得到帧ID,根据帧ID选择校验类型,发送数据段和校验和段。

void LIN_SLAVE_Response(uint8_t *Buffer, uint8_t Length)
{
    uint8_t i = 0;
    uint8_t Checksum = 0, FrameID = 0;

    FrameID  = LIN_PIDToFrameID(LIN_SLAVE_RxBuffer[1]);
    Checksum = 0;

    if ((0x3C == FrameID) || (0x3D == FrameID))
    {
        Checksum = LIN_ClassicChecksum(Buffer, Length);
    }
    else
    {
        Checksum = LIN_EnhancedChecksum(LIN_SLAVE_RxBuffer[1], Buffer, Length);
    }

    for (i = 0; i < Length; i++)
    {
        LIN_SLAVE_SendData(Buffer[i]);
    }

    LIN_SLAVE_SendData(Checksum);
}

3.2 主机程序

主机UART配置

void UART_Configure(uint32_t Baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    UART_InitTypeDef UART_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

    UART_StructInit(&UART_InitStruct);
    UART_InitStruct.BaudRate      = Baudrate;
    UART_InitStruct.WordLength    = UART_WordLength_8b;
    UART_InitStruct.StopBits      = UART_StopBits_1;
    UART_InitStruct.Parity        = UART_Parity_No;
    UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStruct.Mode          = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART1, &UART_InitStruct);

    UART_IDLRConfig(UART1, 100);    /* LIN Master Only!!! */

    UART_ITConfig(UART1, UART_IT_RX, ENABLE);
    UART_ITConfig(UART1, UART_IT_TXBRK, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_7);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_7);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    UART_Cmd(UART1, ENABLE);
}

主机中断服务子程序

void UART1_IRQHandler(void)
{
    uint8_t i = 0;

    if(SET == UART_GetITStatus(UART1, UART_IT_TXBRK))
    {
        UART1_RxLength = 0;

        UART_ClearITPendingBit(UART1, UART_IT_TXBRK);

        UART_ITConfig(UART1, UART_IT_RXIDLE, ENABLE);

        LIN_MASTER_TXBRK_InterruptFlag = 1;
    }

    if(SET == UART_GetITStatus(UART1, UART_IT_RX))
    {
        UART1_RxBuffer[UART1_RxLength] = UART1->RDR & 0x00FF;

        UART1_RxLength = (UART1_RxLength + 1) % 100;

        UART_ClearITPendingBit(UART1, UART_IT_RX);
    }

    if(SET == UART_GetITStatus(UART1, UART_IT_RXIDLE))
    {
        for(i= 0; i < UART1_RxLength; i++)
        {
            LIN_MASTER_RxBuffer[i] = UART1_RxBuffer[i];
        }

        LIN_MASTER_RxLength = UART1_RxLength;
        LIN_MASTER_RxFinish = 1;

        UART_ClearITPendingBit(UART1, UART_IT_RXIDLE);
        UART_ITConfig(UART1, UART_IT_RXIDLE, DISABLE);
    }
}

主机例程

主机间隔500ms发布和接收数据,发送帧ID和数据依次累加:

void UART_LIN_Master_Sample(void)
{
    uint8_t i = 0;
    uint8_t FrameID = 0, Mode = 0;
    uint8_t Buffer[2] = { 0, 0 };

    printf("\r\nTest %s", __FUNCTION__);

    LIN_MASTER_RxLength = 0;
    LIN_MASTER_RxFinish = 0;

    for (i = 0; i < 100; i++)
    {
        LIN_MASTER_RxBuffer[i] = 0;
    }

    UART_Configure(19200);

    while (1)
    {
        if (Mode == 0)
        {
            printf("\r\nLIN Master Write...");
            LIN_Master_SendFrame(FrameID, Buffer, sizeof(Buffer));
        }
        else
        {
            printf("\r\nLIN Master Read....");
            LIN_MASTER_SendHeader(LIN_FrameIDToPID(FrameID));

            while (0 == LIN_MASTER_RxFinish)
            {
            }

            LIN_MASTER_RxFinish = 0;

            printf("\r\nLIN Master Rx Length : %d, Rx Buffer : ", LIN_MASTER_RxLength);

            for (i = 0; i < LIN_MASTER_RxLength; i++)
            {
                printf("0x%02x ", LIN_MASTER_RxBuffer[i]);
            }

            printf("\r\n");

            for (i = 0; i < sizeof(Buffer); i++)
            {
                Buffer[i]++;
            }

            FrameID = (FrameID + 1) % 0x40;
        }

        Mode = (0 == Mode) ? 1 : 0;

        PLATFORM_DelayMS(500);
    }
}

3.3 从机程序

从机UART配置

使能UART LIN总线模式、使能UART接收断开帧中断、使能接收单字节中断。

void UART_Configure(uint32_t Baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    UART_InitTypeDef UART_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

    UART_StructInit(&UART_InitStruct);
    UART_InitStruct.BaudRate      = Baudrate;
    UART_InitStruct.WordLength    = UART_WordLength_8b;
    UART_InitStruct.StopBits      = UART_StopBits_1;
    UART_InitStruct.Parity        = UART_Parity_No;
    UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStruct.Mode          = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART1, &UART_InitStruct);

    UART_LINCmd(UART1, ENABLE);

    UART_ITConfig(UART1, UART_IT_RX, ENABLE);
    UART_ITConfig(UART1, UART_IT_RXBRK, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_7);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_7);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd  = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    UART_Cmd(UART1, ENABLE);
}

从机中断服务子程序

void UART1_IRQHandler(void)
{
    uint8_t i = 0;

    if (SET == UART_GetITStatus(UART1, UART_IT_RXBRK))
    {
        UART1_RxLength = 0;

        UART_ClearITPendingBit(UART1, UART_IT_RXBRK);

        UART_ITConfig(UART1, UART_IT_RXIDLE, ENABLE);
    }

    if (SET == UART_GetITStatus(UART1, UART_IT_RX))
    {
        UART1_RxBuffer[UART1_RxLength] = UART_ReceiveData(UART1);

        UART1_RxLength = (UART1_RxLength + 1) % 100;

        UART_ClearITPendingBit(UART1, UART_IT_RX);
    }

    if (SET == UART_GetITStatus(UART1, UART_IT_RXIDLE))
    {
        for (i = 0; i < UART1_RxLength; i++)
        {
            LIN_SLAVE_RxBuffer[i] = UART1_RxBuffer[i];
        }

        LIN_SLAVE_RxLength = UART1_RxLength;
        LIN_SLAVE_RxFinish = 1;

        UART_ClearITPendingBit(UART1, UART_IT_RXIDLE);
        UART_ITConfig(UART1, UART_IT_RXIDLE, DISABLE);
    }
}

从机例程

从机对帧头包含信息解析,确定是发送应答,还是接收应答。
void UART_LIN_Slave_Sample(void)
{
    uint8_t i = 0;
    uint8_t Checksum = 0, FrameID = 0;
    uint8_t Length   = 0, Buffer[100];

    printf("\r\nTest %s", __FUNCTION__);

    Length             = 0;
    LIN_SLAVE_RxLength = 0;
    LIN_SLAVE_RxFinish = 0;

    for (i = 0; i < 100; i++)
    {
        Buffer[i]             = 0;
        LIN_SLAVE_RxBuffer[i] = 0;
    }

    UART_Configure(19200);

    while (1)
    {
        if (1 == LIN_SLAVE_RxFinish)
        {
            LIN_SLAVE_RxFinish = 0;

            if (0x55 == LIN_SLAVE_RxBuffer[0])
            {
                if (2 == LIN_SLAVE_RxLength)
                {
                    LIN_SLAVE_Response(Buffer, Length);
                }
                else
                {
                    for (i = 2; i < LIN_SLAVE_RxLength - 1; i++)
                    {
                        Buffer[i - 2] = LIN_SLAVE_RxBuffer[i];
                    }

                    Length = LIN_SLAVE_RxLength - 3;
                }
            }
        }
    }
}

3.4 验证

通过UART接口连接两块MM32F5270 MiniBoard,观察串口调试助手:

5.png

先由主机发布数据,从机接收数据,接着由从机发布数据,主机接收数据,依次循环进行。根据截图信息,主从机收发数据一致,与程序逻辑相符,两块板LIN通信成功。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 70

页面

订阅 RSS - MM32F5270