基于Contiki-NG系统设计的IPv6智能插座

项目概述

本文基于contiki-ng系统在硬件平台nrf52840上完成了一种计量插座的设计。插座之间使用IPv6协议族组成一个无线传感网,将采集到功率、电压、电流数据上传到指定IP地址的平台。插座还有一个继电器可以远程控制负载设备通断电。

IPv6网络协议族

IPv6网络协议族

上图展示了整个IPv6协议栈的组成,使用802.15.4标准定义的物理层和链路层,使用时隙跳频(TSCH)技术来降低网络功耗、提高频谱利用率、网络容量、网络抗干扰能力。由于IPv6协议包的较大而802.15.4物理层的数据包长度又有限,在IP层和MAC层之间加了一个数据压缩层来压缩IP数据(6lowpan)。RPL是一种为低功耗有损网络(LLN)而设计的距离矢量路由协议,通过目标函数和度量集合构建一个有向无环图,实现节点的间数据的路由。RPL供定义了4种ICMPv6数据包用来构建网络拓扑分别是DIS、DAO、DIO、DAO ACK。上层使用基于UDP的CoAP协议来传输应用层数据。

硬件设计

现在互联网主要还是以IPv4为骨架而本应用设计的无线传感网是基于IPv6协议栈的,为了使设备可以方便的接入互联网需要解决IPv4和IPv6的互通问题方案有三个,一是隧道技术,二是使用同时支持IPv4、IPv6协议栈的双栈技术,三是地址映射技术,其中NAT64地址映射技术应用最广泛而且易于实现。基于上述原因我们设计了两种硬件但外形上两种硬件相同,一种用来做边界路由器另一种作为普通的智能插座。边界路由器比普通插座多了一个网络模块,网络模块采用联发科的7688实现,运行openwrt系统。

IPv6插座硬件框图

虚线部分只存在于边界路由器普通节点没有。nrf52840运行IPv6协议栈组成无线传感网,通过SLIP协议与网络模块通信,在openwrt中安装kmod-tun内核模块可以在串口构建一个虚拟网卡,这样nrf52840与openwrt相当于通过网线直连交换IPv6数据包。边界路由器将SLIP网口配置为默认网关的出栈口,这样WSN网络中的其它设备就可以通过边界路由器连接到互联网。

IPv6插座外观
IPv6插座内部

实物图片


IPv6插座电参数采集

软件设计

软件设计包括几个部分

  • contiki-ng在硬件平台上的移植
  • 基于tagya的NAT64在openwrt上的安装和配置
  • 安装libcoap
  • 电流、电压、功率采集驱动的编写
  • 应用程序的编写

contiki-ng在硬件平台上的移植

contiki-ng开源系统本身已经支持nrf52840平台但对低功耗的支持并不好,项目开发时对contiki做了一些优化。

  • 使用RTC定时器作为TSCH驱动定时器降低睡眠时MCU功耗
  • 改系统周期tick为事件驱动唤醒,时间作为一种事件类型在进入休眠前根据event timer计算最近的唤醒时间设置RTC定时器到时唤醒,这样可以使MCU长时间处于休眠状态,有需要才唤醒
  • 设计一个支持低功耗叶子节点TSCH调度器,可以根据不同能耗等级使用不同长度的slotframe。
  • 调度器动态插入时隙时需要立即重新计算下次调度时刻,在当前系统即使新插入的时隙的调度的时刻比系统在上次调度后已经预先计算的下次调度时刻要早也还是会按照原来的计算调度,并不会按照实际情况提前调度。

插入时隙立即重新计算下次调度时刻,这个算法主要修改了tsch-slot-operation.c文件:

//在tsch-slot-operation.c中增加下面几个函数,以在动态添加timeslot link时加快调度速度
//在该文件中改变current_slot_start和tsch_current_asn前序调用tsch_record_pre_state函数记录他们之前的值

static rtimer_clock_t volatile current_slot_start_pre = 0;
static struct tsch_asn_t tsch_current_asn_pre;
static void tsch_record_pre_state(rtimer_clock_t t, struct tsch_asn_t asn) {
  current_slot_start_pre = t;
  tsch_current_asn_pre = asn;
}

void tsch_slot_schedule_immediate(void) {
  int r = 0;
  if(tsch_in_slot_operation) {
    return;
  }
  do {
    rtimer_clock_t time_to_next_active_slot;
    uint16_t timeslot_diff;
    int_master_status_t status = critical_enter();
    rtimer_clock_t passed = RTIMER_NOW() - current_slot_start_pre;
    uint32_t passed_diff = passed/tsch_timing[tsch_ts_timeslot_length];
    int32_t diff = TSCH_ASN_DIFF(tsch_current_asn, tsch_current_asn_pre);
    uint32_t reamin_diff = diff - passed_diff;
    if(reamin_diff < 5) {
      critical_exit(status);
      return;
    }
    // rtimer_clock_t timeslot_remain = tsch_timing[tsch_ts_timeslot_length] - (passed-(passed_diff*tsch_timing[tsch_ts_timeslot_length]));
    // RTIMER_BUSYWAIT_UNTIL_ABS(0, RTIMER_NOW(), timeslot_remain);
    tsch_current_asn = tsch_current_asn_pre;
    current_slot_start = current_slot_start_pre + (passed_diff+2)*tsch_timing[tsch_ts_timeslot_length];
    TSCH_ASN_INC(tsch_current_asn, (passed_diff+2)); //+2防止下次调度距离当前时间太近
    tsch_record_pre_state(current_slot_start, tsch_current_asn);
    // tsch_slot_operation_sync(current_slot_start, &tsch_current_asn);
    /* Get next active link */
    current_link = tsch_schedule_get_next_active_link(&tsch_current_asn, ×lot_diff, &backup_link);
    if(current_link == NULL) {
      /* There is no next link. Fall back to default
       * behavior: wake up at the next slot. */
      timeslot_diff = 1; 
    }
    TSCH_ASN_INC(tsch_current_asn, timeslot_diff);
    time_to_next_active_slot = timeslot_diff*tsch_timing[tsch_ts_timeslot_length];
    /* Compensate for the base drift */
    time_to_next_active_slot += tsch_timesync_adaptive_compensate(time_to_next_active_slot);
    current_slot_start += time_to_next_active_slot;
    //重新根据计算timeslot设置rtimer唤醒时间
    r = rtimer_set(&slot_operation_timer, current_slot_start, 1, (void (*)(struct rtimer *, void *))tsch_slot_operation, NULL);
    if(r == RTIMER_OK) {
      rtimer_arch_schedule(current_slot_start);
    }
    critical_exit(status);
  } while(r != RTIMER_OK);
  return;
}

TSCH调度器等实现可以参看我的github。

SLIP网口配置

tunslip6是contiki-ng提供的一个支持SLIP协议应用层程序,基本原理是接收串口的数据通过kmod-tun转发到Linux内核,再将内核通过kmod-tun路由过来的数据转发到串口,这样串口就虚拟成了一个网卡可以和内核交换IP数据包。tun0为虚拟网卡名,虚拟网卡生成后和普通网卡一样可以用系统提供的所有网络工具进行配置。

配置串口虚拟网卡:

#!/bin/sh
# 工具程序在 /contiki-ng/tools/serial-io 文件夹下
# =======================正常启动模式===========================
/root/app/slip-nat64/tunslip6 -s /dev/ttyS1 2001:db8::/64 &

sleep 2
# ip addr add 2001:db8::01 dev tun0
ip addr add 2001:db8::a00:1 dev tun0

tagya库在openwrt上的安装和配置

tagya是NAT64的一个应用层实现与内核也是用kmod-tun模块虚拟网卡实现IP数据包交互,通过配置一些指定的目标IP地址被路由到tagya,处理后再返回内核。最新的opewrt系统建议使用另一个名为Jool的库来实现。

使用opkg安装tagya, opkg update后要重启openwrt,否则可能会安装包时可能会提示安装包不匹配。

opkg update
opkg tayga

或者下载tayga源码直接安装,tayga源码比较简单,一般编译不会有问题。

NAT64配置脚本:

#!/bin/sh
NAT64_PREFIX=fdaa:add::/96  
#NAT64_PREFIX=fd69:7910::/96  
# NAT64_PREFIX="64:ff9b::/96"          # 使用此前缀tayga.conf文件中必须指定ipv6-addr
NAT64_POOL=10.0.0.0/8
NAT64_IPv4=10.1.1.1

cat >/etc/tayga.conf <tun--->tayga,tayga从地址池中选取一个源地址替换源地址并跟住IPv6地址生成IPv4地址替换目标地址
# 后将数据包通过tun转发会IP协议栈
ip route add $NAT64_PREFIX dev nat64  # 和tayga.conf中prefix对应
# NAT44映射,源地址映射,将IP数据包源地址为10.0.0.0/8(tayga地址池)映射为192.168.1.232(WAN口IPv4地址)
# 这样通过WAN口发出去请求数据包的响应包可以被路由到WAN口,否则响应数据包无法返回(外部路由器不知道内网的IP)
# 下面指令也可以指定具体的网络接口(-o br-lan),当WAN地址不固定时可以用 -j MASQUERADE选项其是-j SNAT选项的一个特例
# iptables -t nat -A zone_lan_postrouting -s 10.0.0.0/8 -j SNAT --to-source 192.168.1.232
# iptables -t nat -A zone_lan_postrouting -s $NAT64_POOL -j MASQUERADE  # added by tomi
# iptables -t nat -A POSTROUTING -s $NAT64_POOL -j MASQUERADE  # added by tomi
# TODO:如果本机充当路由器,这样替换后丢失了源地址信息其它接入本机的设备请求返回的数据将无从知道这个数据包转发到何处
# 解决办法NAPT,需要添加一下Nat规则。
iptables -t nat -A POSTROUTING -j MASQUERADE   # added by tomi 

# add NAT64 firewall entries (required for LEDE)
iptables -A forwarding_rule -i nat64 -j ACCEPT
ip6tables -A forwarding_rule -o nat64 -j ACCEPT

/sbin/sysctl -w net.ipv4.conf.all.forwarding=1
/sbin/sysctl -w net.ipv6.conf.all.forwarding=1

tayga -c /etc/tayga.conf &

一开始配置NAT64总是不起作用,这时要检查iptable中WAN口关于IPv4的规则链配置,具体见shell中的注释。关于linux中IP数据包的路由,iptable的配置参见相关文章。tayga会依据虚拟网卡的路由配置收到需要转换的数据包,然后根据一定的变换规则相互转化IPv4、IPv6数据包并记录转换映射表,方便返回数据的转换。

NAT64只是参考下面博文

  • 博文1

  • 博文2

#    下图演示了上层APP或slip网络设备通过IPv6访问8.8.8.8的过程:
#    1、tayga和slip设备都通过kmod-tun和内核协议栈做双向通信
#    2、slip和tayga要用不同的tun设备,且设备要用不同的前缀
#    3、nat44将src: 10.x.x.x/8 转换成src: 192.168.1.232后通过wan口发出,这样外部路由器可以将数据路由到本机(是否会进行nat44逆转换?)

#                                          ------------------        ------------------
#                dynamic-pool: 10.x.x.x/8 |                  |      |                  |
#                                         |       tayga      |      |       APP        |
#                                         |                  |      |                  |
#                                          ------------------        ------------------
#                         nat64 packet:   /|\     |       /|\                |  orig packet:
#                         src: 10.x.x.x/8  |      |        |                 |  src: x:x:x:x:x:x:x:x
#                         dst: 8.8.8.8           tun                         |  dst: fd69:7910::8.8.8.8 (匹配:ip route add fd69:7910:aaaa:ffff::/96 dev nat64因此转发到tayga)
#                                          |      |        |                 |
#                                      ----|------|--------|-----------------|--------------
#                                     |    |      |        |_________________|              |
#                                     |    |      |      TCP/IP statck                      |
#                                     |    |      |_________________________     /|\        |
#                                      ----|--------------------------------|-----|----------
#                 src: x:x:x:x:x:x:x:x     |_____        nat44 :            |     | 匹配ip route add 192.168.255.0/24 dev nat64转发到tayga后进行nat46然后发回stack进行路由,这里不具体画出路径
#                 dst: fd69:7910::8.8.8.8        |       src: 192.168.1.232 |     | src: 8.8.8.8
#                                               tun      dst: 8.8.8.8      \|/    | dst: 192.168.255.xx
#                                          ------|-----------        ------------------  
#                                         |                  |      |                  | WAN info:  
#                                         |       slip       |      |     eth/wifi     | IPv4: 192.168.1.232  
#                                         |                  |      |                  | IPv6: fc69:7910:aaaa::1
#                                          ------------------        ------------------

安装libcoap

libcoap是一个开源的coap协议库。

libcoap要从源码编译安装,交叉编译很顺利但是libcoap库examples中生成的目标程序都是shell文件,coap-server这个shell程序在openwrt运行会有很多报错。解决办法是coap-server在宿主机上先运行一遍,虽然肯定运行不成功(目标文件cpu架构不同),但是这样运行一遍后会在examples文件夹下生成.libs目录,这个目录中会有一个elf格式的coap-server文件,将其拷贝到openwrt系统中即可正常运行。

电流、电压、功率采集驱动

BL0937芯片是根据电流、电压、功率的大小输出不同频率的脉冲,驱动程序主要通过定时器事件对两个脉冲之间的时间间隔进行记时,配合适当的滤波程序计算目标值。

#include "contiki.h"
#include "boards.h"
#include "gpio-hal.h"
#include "sys/int-master.h"
#include "sys/critical.h"
#include "nrf_ppi.h"
#include "nrfx_ppi.h"
#include "nrf_timer.h"

// #define DBG_ENABLE
// #define DBG_LEVEL DBG_INFO
// #define USING_SEGGER_RTT
// #include "dbgprint.h"

// #define MCU_SLEEP 1

#define VOLTAGE_SAMPLE_TIME  100  //ms
#define CURRENT_SAMPLE_TIME  500 //ms
static int8_t current_voltage_sel = 0, current_voltage_state = 0, power_state = 0; 
static uint32_t current_voltage_int_count = 0;
static uint8_t voltage_clk_count = 0, current_clk_count = 0, power_clk_count = 0;
static uint32_t voltage_clk_last = 0, voltage_clk[5], voltage_clk_metering = 0;
static uint32_t current_clk_last = 0, current_clk[5], current_clk_metering = 0;
static uint32_t power_clk_last = 0, power_clk[5], power_clk_metering = 0;
#if MCU_SLEEP
PROCESS(electric_main, "electric main");
#else
static struct ctimer metering_timer;
#endif

#define TIMER_INSTANCE NRF_TIMER1

float get_electric_mains_current() {
    int_master_status_t status = critical_enter();
    uint32_t clk = current_clk_metering;
    critical_exit(status);
    if(clk == 0) {
        return 0;
    }
    float hz = (16000000/(nrf_timer_frequency_get(TIMER_INSTANCE)+1))/(float)clk;
    float c = (1.218*hz/94638)/0.001;    //0.001: 1mohm采样电阻
    c = c*0.9;
    if(c < 0.005) {
        c = 0;
    }
    return c > 12 ? 12 : c;
}

float get_electric_mains_voltage() {
    int_master_status_t status = critical_enter();
    uint32_t clk = voltage_clk_metering;
    critical_exit(status);
    if(clk == 0) {
        return 0;
    }
    float hz = (16000000/(nrf_timer_frequency_get(TIMER_INSTANCE)+1))/(float)clk;
    float v = 1801*1.218*hz/15397;    //1801:电阻分压
    v *= 0.85; 
    if(v < 180) {
        v = 180;
    }
    if(v > 255) {
        v = 255;
    }
    return v;//v > 235 ? v - 32 : v;
}

float get_electric_mains_power() {
    uint32_t clk;
    int_master_status_t status = critical_enter();
    clk = power_clk_metering;
    critical_exit(status);
    if(clk == 0) {
        return 0;
    }
    float hz = (16000000/(nrf_timer_frequency_get(TIMER_INSTANCE)+1))/(float)clk;
    float p = 1801000*1.218*1.218*hz/1721506;//(1801*1.218*1.218*hz/1721506)/0.001;    //1801:电阻分压 0.001: 1mohm残阳电阻
    if(p < 0.1) {
        p = 0.0;
    }
    if(p > 2500) {
        p = 2500;
    }
    return p;
    // return get_electric_mains_current()*get_electric_mains_voltage();
}

static uint32_t filter_value(uint32_t *data, int nbr) {
    if(nbr < 3) {
        return 0;
    }
    uint32_t max = 0, min=-1, sum = 0;
    for(int i = 0; i < nbr; i++) {
        if(data[i] > max) {
            max = data[i];
        }
        if(data[i] < min) {
            min = data[i];
        }

        sum += data[i];
    }
    return (sum - max - min)/(nbr-2);
}

void electric_mains_current_voltage_freq_isr() {
    current_voltage_int_count++;
    current_voltage_state++;
    uint32_t timestamps = nrf_timer_cc_read(TIMER_INSTANCE, NRF_TIMER_CC_CHANNEL0);
    if(current_voltage_sel) {
        if(current_voltage_state == 1) {
            voltage_clk_last = timestamps;
        }
        if(current_voltage_state > 1) {
            // voltage_clk = ((timestamps - voltage_clk_last) + voltage_clk)>>1;
            uint32_t clk = timestamps - voltage_clk_last;
            // if(count < 3 && (clk > voltage_clk*1.5 || clk < voltage_clk*0.5)) {
            //     count++;
            // } else {
            //     voltage_clk = (timestamps - voltage_clk_last)*0.1 + voltage_clk*0.9;
            //     count = 0;
            // }
            if(voltage_clk_count < sizeof(voltage_clk)/sizeof(voltage_clk[0])) {
                voltage_clk[voltage_clk_count++] = clk;
            } else {
                voltage_clk_metering = filter_value(voltage_clk, voltage_clk_count);
                // int_master_status_t status = critical_enter();
                // voltage_clk_metering = voltage_clk;
                // critical_exit(status);
                voltage_clk_count = 0;
            }
            
            voltage_clk_last = timestamps;
        }
    } else {
        if(current_voltage_state == 1) {
            current_clk_last = timestamps;
        }
        if(current_voltage_state > 1) {
            uint32_t clk = timestamps - current_clk_last;
            // current_clk = ((timestamps - current_clk_last) + current_clk)>>1;
            // current_clk = (timestamps - current_clk_last)*0.05 + current_clk*0.95;
            if(current_clk_count < sizeof(current_clk)/sizeof(current_clk[0])) {
                current_clk[current_clk_count++] = clk;
            } else {
                current_clk_metering = filter_value(current_clk, current_clk_count);
                current_clk_count = 0;
            }
            current_clk_last = timestamps;
        }
    }
}

void electric_mains_power_freq_isr() {
    power_state++;
    uint32_t timestamps = nrf_timer_cc_read(TIMER_INSTANCE, NRF_TIMER_CC_CHANNEL1);
    if(power_state&0x01) {
        power_clk_last = timestamps;
    } else {
        // power_clk = ((timestamps - power_clk_last) + power_clk)>>1;
        // power_clk = (timestamps - power_clk_last)*0.05 + power_clk*0.95;
        if(power_clk_count < sizeof(power_clk)/sizeof(power_clk[0])) {
            power_clk[power_clk_count++] = timestamps - power_clk_last;
        } else {
            power_clk_metering = filter_value(power_clk, power_clk_count);
            power_clk_count = 0;
        }
    }
}

static void set_current_voltage_selector(int sel) {
    current_voltage_int_count = 0;
    //测量切换时设为-1,跳过第一个上升沿,防止出错
    current_voltage_state = -1;
    if(sel) {
        current_voltage_sel = 1;
        gpio_hal_arch_set_pin(0, 24);   //voltage
    } else {
        current_voltage_sel = 0;
        gpio_hal_arch_clear_pin(0, 24); //current
    }
}


static void metering_timeout() {
    int_master_status_t status = critical_enter();
    //切换测量电压电流
    if(current_voltage_sel) {
        if(current_voltage_state == -1 || current_voltage_int_count < 2) {
            //在CURRENT_VOLTAGE_CHANGE_TIME时间内没有产生中断,认为被测量值太小,检测不到
            // voltage_clk = 0;
            voltage_clk_metering = 0;
            voltage_clk_count = 0;
        } else {

        }
        set_current_voltage_selector(0);
    } else {
        if(current_voltage_state == -1 || current_voltage_int_count < 2) {
            //在CURRENT_VOLTAGE_CHANGE_TIME时间内没有产生中断,认为被测量值太小,检测不到
            // current_clk = 0;
            current_clk_metering = 0;
            power_clk_metering = 0;
            current_clk_count = 0;
            power_clk_count = 0;
        } else {

        }
        set_current_voltage_selector(1);
    }

    critical_exit(status);
}

static void metering_timer_cb() {
    metering_timeout();

    if(current_voltage_sel) {
        ctimer_set(&metering_timer, CLOCK_SECOND/(1000/VOLTAGE_SAMPLE_TIME), metering_timer_cb, 0);
    } else {
        ctimer_set(&metering_timer, CLOCK_SECOND/(1000.0/CURRENT_SAMPLE_TIME), metering_timer_cb, 0);
    }
}

static void
cv_int_handler(gpio_hal_port_t port, gpio_hal_pin_mask_t pin_mask) {
    electric_mains_current_voltage_freq_isr();
}

static void
power_int_handler(gpio_hal_port_t port, gpio_hal_pin_mask_t pin_mask) {
    electric_mains_power_freq_isr();
}


/*---------------------------------------------------------------------------*/
static gpio_hal_event_handler_t cv_handler = {
  .next = NULL,
  .handler = cv_int_handler,
  .port = 1,
  .pin_mask = gpio_hal_pin_to_mask(13),
};

static gpio_hal_event_handler_t power_handler = {
  .next = NULL,
  .handler = power_int_handler,
  .port = 0,
  .pin_mask = gpio_hal_pin_to_mask(2),
};


static nrf_ppi_channel_t ppi_ch = -1, ppi_ch_power = -1;

void electrical_mains_metering_config(void) {
    gpio_hal_pin_cfg_t cfg;
    SystemCoreClockUpdate();
    // SEl
    cfg = GPIO_HAL_PIN_CFG_EDGE_RISING|GPIO_HAL_PIN_CFG_PULL_NONE|GPIO_HAL_PIN_CFG_INT_DISABLE;
    gpio_hal_arch_port_pin_cfg_set(0, 24, cfg);
    gpio_hal_arch_pin_set_output(0, 24);

    // power pluse
    gpio_hal_register_handler(&power_handler);
    cfg = GPIO_HAL_PIN_CFG_EDGE_RISING|GPIO_HAL_PIN_CFG_PULL_NONE|GPIO_HAL_PIN_CFG_INT_ENABLE;
    gpio_hal_arch_port_pin_cfg_set(0, 2, cfg);
    
    // current voltage pluse
    gpio_hal_register_handler(&cv_handler);
    cfg = GPIO_HAL_PIN_CFG_EDGE_RISING|GPIO_HAL_PIN_CFG_PULL_NONE|GPIO_HAL_PIN_CFG_INT_ENABLE;
    gpio_hal_arch_port_pin_cfg_set(1, 13, cfg);  

    
    nrf_timer_frequency_set(TIMER_INSTANCE, NRF_TIMER_FREQ_16MHz);
    nrf_timer_bit_width_set(TIMER_INSTANCE, NRF_TIMER_BIT_WIDTH_32);
    nrf_timer_mode_set(TIMER_INSTANCE, NRF_TIMER_MODE_TIMER);

    nrfx_err_t err = nrfx_ppi_channel_alloc(&ppi_ch);
    if (err != NRFX_SUCCESS) {
        return;
    }
    err = nrfx_ppi_channel_alloc(&ppi_ch_power);
    if (err != NRFX_SUCCESS) {
        return;
    }

    // 配置捕获事件
    // uint32_t pin_number         = NRF_GPIO_PIN_MAP(1, 13);
    // int32_t             channel = (int32_t)channel_port_get(pin_number);
    // nrf_gpiote_events_t event   = TE_IDX_TO_EVENT_ADDR((uint32_t)channel);
    nrf_ppi_channel_endpoint_setup(
        ppi_ch,
        (uint32_t)nrf_gpiote_event_addr_get(NRF_GPIOTE_EVENTS_IN_2),  //NRF_GPIOTE_EVENTS_IN_2 根据gpio_hal_arch_port_pin_cfg_set内部设置决定
        (uint32_t)nrf_timer_task_address_get(TIMER_INSTANCE, NRF_TIMER_TASK_CAPTURE0));

    nrf_ppi_channel_endpoint_setup(
        ppi_ch_power,
        (uint32_t)nrf_gpiote_event_addr_get(NRF_GPIOTE_EVENTS_IN_1),
        (uint32_t)nrf_timer_task_address_get(TIMER_INSTANCE, NRF_TIMER_TASK_CAPTURE1));

    nrf_ppi_channel_enable(ppi_ch_power);
    nrf_ppi_channel_enable(ppi_ch);
    nrf_timer_task_trigger(TIMER_INSTANCE, NRF_TIMER_TASK_START);

#if MCU_SLEEP
    process_start(&electric_main, NULL);
#else   
    // uint32_t core_debug = CoreDebug->DEMCR;
    // CoreDebug->DEMCR = core_debug | CoreDebug_DEMCR_TRCENA_Msk;
    // // Save the current state of the CTRL register in the DWT block. Make sure
    // // that the cycle counter is enabled.
    // uint32_t dwt_ctrl = DWT->CTRL;
    // DWT->CTRL = dwt_ctrl | DWT_CTRL_CYCCNTENA_Msk;

    ctimer_set(&metering_timer, CLOCK_SECOND/(1000.0/VOLTAGE_SAMPLE_TIME), metering_timer_cb, 0);
    set_current_voltage_selector(1);
#endif  
}

void
metering_timer_uinit(void)
{
  #define DISABLE_ALL UINT32_MAX
  nrf_timer_shorts_disable(TIMER_INSTANCE, DISABLE_ALL);
  nrf_timer_int_disable(TIMER_INSTANCE, DISABLE_ALL);
  nrf_timer_event_clear(TIMER_INSTANCE, NRF_TIMER_EVENT_COMPARE0);
  nrf_timer_event_clear(TIMER_INSTANCE, NRF_TIMER_EVENT_COMPARE1);
  nrf_timer_event_clear(TIMER_INSTANCE, NRF_TIMER_EVENT_COMPARE2);
  nrf_timer_event_clear(TIMER_INSTANCE, NRF_TIMER_EVENT_COMPARE3);
  #undef DISABLE_ALL

  nrf_ppi_channel_disable(ppi_ch);
  nrfx_ppi_channel_free(ppi_ch);
  nrf_ppi_channel_disable(ppi_ch_power);
  nrfx_ppi_channel_free(ppi_ch_power);
  nrf_timer_task_trigger(TIMER_INSTANCE, NRF_TIMER_TASK_SHUTDOWN);
}

#if MCU_SLEEP

PROCESS_THREAD(electric_main, ev, data) {
    static struct etimer periodic_timer;
    PROCESS_BEGIN();
    set_current_voltage_selector(1);
    etimer_set(&periodic_timer, CLOCK_SECOND);
    while(1) {
        PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&periodic_timer));

        uint32_t core_debug = CoreDebug->DEMCR;
        CoreDebug->DEMCR = core_debug | CoreDebug_DEMCR_TRCENA_Msk;
        // Save the current state of the CTRL register in the DWT block. Make sure
        // that the cycle counter is enabled.
        uint32_t dwt_ctrl = DWT->CTRL;
        DWT->CTRL = dwt_ctrl | DWT_CTRL_CYCCNTENA_Msk;

        nrfx_gpiote_in_event_enable(NRF_GPIO_PIN_MAP(1,13), true);
        etimer_set(&periodic_timer, CLOCK_SECOND / (1000.0 / VOLTAGE_SAMPLE_TIME));
        do {
            process_poll(&electric_main);   //防止测量期间休眠
            PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER || ev == PROCESS_EVENT_POLL);
        } while (ev == PROCESS_EVENT_POLL);
        nrfx_gpiote_in_event_disable(NRF_GPIO_PIN_MAP(1,13));
        metering_timeout();


        // set_current_voltage_selector(0);
        nrfx_gpiote_in_event_enable(NRF_GPIO_PIN_MAP(1,13), true);
        etimer_set(&periodic_timer, CLOCK_SECOND/(1000.0/CURRENT_SAMPLE_TIME));
        do {
            process_poll(&electric_main);   //防止测量期间休眠
            PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER || ev == PROCESS_EVENT_POLL);
        } while (ev == PROCESS_EVENT_POLL);
        nrfx_gpiote_in_event_disable(NRF_GPIO_PIN_MAP(1,13));
        
        // Restore preserved registers.
        DWT->CTRL = dwt_ctrl;
        CoreDebug->DEMCR = core_debug;
        
        metering_timeout();

        etimer_set(&periodic_timer, CLOCK_SECOND*5);
    }
    PROCESS_END();
}
#endif

应用程序

contiki-ng中coap相关代码有些bug做些修正,原代码先返回coap响应再处理coap请求,这样存在一个bug,先返回数据会破坏缓冲区收到的请求数据导致obs->notification_callback函数无法正确处理,因此要调换下处理流程。
coap-observe-client.c: 修改如下

  --- if(notification->type == COAP_TYPE_CON) {
  ---   simple_reply(COAP_TYPE_ACK, endpoint, notification);
  --- }
  if(obs->notification_callback != NULL) {
    flag = classify_notification(notification, 0);
    /* TODO: the following mechanism for discarding duplicates is too trivial */
    /* refer to Observe RFC for a better solution */
    if(flag == NOTIFICATION_OK) {
      coap_get_header_observe(notification, &observe);
      if(observe == obs->last_observe) {
        LOG_DBG("Discarding duplicate\n");
        return;
      }
      obs->last_observe = observe;
    }
    obs->notification_callback(obs, notification, flag);
  }
  +++ if(notification->type == COAP_TYPE_CON) {
  +++  simple_reply(COAP_TYPE_ACK, endpoint, notification);
  +++ }

展望

本项目的开发过程基本解决contiki-ng和openwrt配合实现一种可用的WSN网络的一些问题,虽然对市电供电智能插座而言低功耗不是关键问题,但是对一些电池供电的环境采集应用而言低功耗就是一个关键考量因素了。这个项目在室内应用的场景下,可以以插座为骨架扩展其它IPv6网络设备实现不同应用,其它应用可以以此为基础设计不同的硬件和应用程序。

你可能感兴趣的:(基于Contiki-NG系统设计的IPv6智能插座)