本项目以RISC-V架构的D1 Dock Pro和D1 Nezha开发板为硬件平台,应用物联网和区块链技术,设计开发一套分布式能源智慧管理小型示范系统,在该系统上实现能源生产和消费数据的实时监测。该项目在“玄铁杯第二届RISC-V应用创新大赛”活动中荣获一等奖。
关于分布式能源智慧管理和M2M交易系统的技术细节,请看本文详细介绍。
01 项目介绍
能源和环保是关乎人类未来的重要课题。为实现碳中和目标,大力发展可再生清洁能源以替代传统化石能源,提高能源系统监控和消费的智能化水平,是可行的重要途径之一。本项目以RISC-V架构的D1 Dock Pro和D1 Nezha开发板为硬件平台,应用物联网和区块链技术,设计开发一套分布式能源智慧管理小型示范系统,在该系统上实现能源生产和消费数据的实时监测。
02 技术方案
项目使用 D1 Dock Pro 开发板设计开发一款专用网关,实时采集电池控制器、气象环境传感器等其它传感器的数据,并通过无线通信方式(WiFi)以HTTP协议或MQTT协议将传感器数据上传至物联网后台。
图1.专用网关示意
图2.专用网关实物图
智能开关用于能源消费端,实现对能源消费者(电器负载)的供电控制、电能消费数据的采集和传输等功能。该智能开关基于 D1 Dock Pro 开发板进行设计开发,通过开发板的I/O口控制继电器、UART接收电能计量模块的数据。设计一个扩展电路板与开发板配合使用, 扩展电路板集成电能计量模块、继电器等。本文设计的智慧开关的功能主要是控制电器开关与计量电器用电参数以及环境参数并上传到云端服务器。
图3.智能开关示意图
图4.智能开关实物图
03 核心业务代码
3.1 智能开关电能采集分析
// sensor variable float sensor_data[9] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9}; int recv_cmd; unsigned long Voltage_data, Current_data, Power_data, Energy_data, Pf_data, CO2_data, HZ; // read the consumer date void read_consumer_data(void) { //send the instruction unioncrc_data { unsigned int word16; unsigned char byte[2]; } crc_now; tx_buffer[0] = 0x01; tx_buffer[1] = 0x03; tx_buffer[2] = 0x01; tx_buffer[3] = 0x00; tx_buffer[4] = 0x02; tx_buffer[5] = 0x08; crc_now.word16 = chk_crc(tx_buffer, 6); tx_buffer[6] = crc_now.byte[1];//CRC verification tx_buffer[7] = crc_now.byte[0]; ret = csi_uart_send_async(&g_uart, tx_buffer,8); //wait until send finished while(1) { if (tx_async_flag) { tx_async_flag = 0; break; } } printf("send succeed "); ret = csi_uart_receive_async(&g_uart, rx_buffer, 1); //wait until receieve finished while(1) { // printf("not_receieved "); aos_msleep(200); if (rx_async_flag) { break; } } printf("Line 358: got data"); parse_data(); publish_sensor_data(client, "publish"); } //analyze the consumer date void parse_data(void) { csi_error_t ret; unsigned char i; union crc_data { unsigned int word16; unsigned char byte[2]; } crc_now; if (rx_async_flag == 1){ // check if receieve finished rx_async_flag = 0; if ((rx_buffer[0] == 0x01)) { //check the ID of the device crc_now.word16 = chk_crc(rx_buffer, recieve_data_num - 2);//crc verification if ((crc_now.byte[0] == rx_buffer[recieve_data_num - 1]) && (crc_now.byte[1] == rx_buffer[recieve_data_num - 2])) { //parse voltage Voltage_data = (((unsigned long)(rx_buffer[3])) << 24) | (((unsigned long)(rx_buffer[4])) << 16) | (((unsignedlong)(rx_buffer[5])) << 8) | rx_buffer[6]; sensor_data[0] = (float)(Voltage_data * 0.0001); //parse current Current_data = (((unsigned long)(rx_buffer[7])) << 24) | (((unsigned long)(rx_buffer[8])) << 16) | (((unsignedlong)(rx_buffer[9])) << 8) | rx_buffer[10]; sensor_data[1] = (float)(Current_data * 0.0001); //parse power Power_data = (((unsignedlong)(rx_buffer[11])) << 24) | (((unsigned long)(rx_buffer[12])) << 16) | (((unsignedlong)(rx_buffer[13])) << 8) | rx_buffer[14]; sensor_data[2] = (float)(Power_data * 0.0001); //parse energy Energy_data = (((unsignedlong)(rx_buffer[15])) << 24) | (((unsigned long)(rx_buffer[16])) << 16) | (((unsignedlong)(rx_buffer[17])) << 8) | rx_buffer[18]; sensor_data[3] = (float)(Energy_data * 0.0001); //parse power factor Pf_data = (((unsignedlong)(rx_buffer[19])) << 24) | (((unsigned long)(rx_buffer[20])) << 16) | (((unsignedlong)(rx_buffer[21])) << 8) | rx_buffer[22]; sensor_data[4] = (float)(Pf_data * 0.001); //parse CO2 CO2_data = (((unsigned long)(rx_buffer[23])) << 24) | (((unsigned long)(rx_buffer[24])) << 16) | (((unsignedlong)(rx_buffer[25])) << 8) | rx_buffer[26]; sensor_data[5] = (float)(CO2_data * 0.0001); //parse frequency of the Single phase alternating current HZ = (((unsigned long)(rx_buffer[31])) << 24) | (((unsigned long)(rx_buffer[32])) << 16) | (((unsignedlong)(rx_buffer[33])) << 8) | rx_buffer[34]; sensor_data[6] = (float)(HZ * 0.01); } else { printf("CRC_error "); } } } else { printf("receieve_not_finished "); } } // EOF uart
3.2 MQTT电能数据上云
// mqtt char pub_topic[] = "wattnode/data"; char sub_topic[] = "wattnode/cmd"; mqtt_client_t *client; int is_mqtt_ready = 0; void mqtt_do_connect(mqtt_client_t *client); static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags); void publish_sensor_data(mqtt_client_t *client, void *arg); /* Called when publish is complete either with sucess or failure */ static void mqtt_pub_request_cb(void *arg, err_t result) { if(result != ERR_OK) { printf("Publish result: %d ", result); } } /* The idea is to demultiplex topic and create some reference to be used in data callbacks Example here uses a global variable, better would be to use a member in arg If RAM and CPU budget allows it, the easiest implementation might be to just take a copy of the topic string and use it in mqtt_incoming_data_cb */ static int inpub_id; static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { printf("Incoming publish at topic %s with total length %u ", topic, (unsigned int)tot_len); /* Decode topic string into a user defined reference */ if(strcmp(topic, "print_payload") == 0) { inpub_id = 0; } else if(topic[0] == 'A') { /* All topics starting with 'A' might bwhile(1)e handled at the same way */ inpub_id = 1; } else { /* For all other topics */ inpub_id = 2; } } static void mqtt_sub_request_cb(void *arg, err_t result) { /* Just print the result code here for simplicity, normal behaviour would be to take some action if subscribe fails like notifying user, retry subscribe or disconnect from server */ printf("Subscribe result: %d ", result); } static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { err_t err; if(status == MQTT_CONNECT_ACCEPTED) { printf("mqtt_connection_cb: Successfully connected "); /* Setup callback for incoming publish requests */ mqtt_set_inpub_callback(client, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, arg); /* Subscribe to a topic named "subtopic" with QoS level 1, call mqtt_sub_request_cb with result */ err = mqtt_subscribe(client, sub_topic, 1, mqtt_sub_request_cb, arg); if(err != ERR_OK) { printf("mqtt_subscribe return: %d ", err); } printf("ready to read data"); is_mqtt_ready = 1; } else { printf("mqtt_connection_cb: Disconnected, reason: %d ", status); /* Its more nice to be connected, so try to reconnect */ mqtt_do_connect(client); } } static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { printf("Incoming publish payload with length %d, flags %u ", len, (unsigned int)flags); if(flags & MQTT_DATA_FLAG_LAST) { /* Last fragment of payload received (or whole part if payload fits receive buffer See MQTT_VAR_HEADER_BUFFER_LEN) */ /* Call function or do action depending on reference, in this case inpub_id */ if(inpub_id == 0) { /* Don't trust the publisher, check zero termination */ if(data[len-1] == 0) { //printf("mqtt_incoming_data_cb: %s ", (const char *)data); } } else if(inpub_id == 1) { /* Call an 'A' function... */ } else { // printf("mqtt_incoming_data_cb: Ignoring payload... "); // printf("mqtt_incoming_data_cb: %s ", (const char *)data); recv_cmd = atoi((const char *)data); printf("receive data: %d ", recv_cmd); } } else { /* Handle fragmented payload, store in buffer, write to file or whatever */ } } void publish_sensor_data(mqtt_client_t *client, void *arg) { err_t err; u8_t qos = 0; /* 0 1 or 2, see MQTT specification */ u8_t retain = 0; /* No don't retain such crappy payload... */ const int LEN = 9; // cat all float data to string char sep = ';'; // char *prefix = "data="; char *prefix = ""; char _str_data[10]; char post_str[128]; strcpy(post_str, prefix); for (int i = 0; i < LEN-1; i++) { sprintf(_str_data, "%.3f", sensor_data[i]); _str_data[strlen(_str_data)-1] = sep; strcat(post_str, _str_data); } sprintf(_str_data, "%.3f", sensor_data[LEN-1]); strcat(post_str, _str_data); err = mqtt_publish(client, pub_topic, post_str, strlen(post_str), qos, retain, mqtt_pub_request_cb, arg); if(err != ERR_OK) { printf("Publish err: %d ", err); } } void mqtt_do_connect(mqtt_client_t *client) { struct mqtt_connect_client_info_t ci; err_t err; ip4_addr_t ip_addr; IP4_ADDR(&ip_addr, 106, 14, 44, 95); /* Setup an empty client info structure */ memset(&ci, 0, sizeof(ci)); /* Minimal amount of information required is client identifier, so set it here */ ci.client_id = "wattnode1"; /* Initiate client and connect to server, if this fails immediately an error code is returned otherwise mqtt_connection_cb will be called with connection result after attempting to establish a connection with the server. For now MQTT version 3.1.1 is always used */ err = mqtt_client_connect(client, &ip_addr, MQTT_PORT, mqtt_connection_cb, 0, &ci); /* For now just print the result code if something goes wrong */ if(err != ERR_OK) { printf("mqtt_connect return %d ", err); } } static void mqtt_main_task(void *d) { printf("Enter mqtt_main_task "); // mqtt_client_t *client = mqtt_client_new(); client = mqtt_client_new(); if(client != NULL) { mqtt_do_connect(client); } } // EOF mqtt
3.3 智能开关
// switch variable const int SWITCH_PIN = PC1; static csi_gpio_pin_t pin; #define GPIO_CHECK_RETURN(ret) do { if (ret != CSI_OK) { return -1; } } while(0); //init the switch void switch_init() { ret = csi_gpio_pin_init(&pin, SWITCH_PIN); GPIO_CHECK_RETURN(ret); /* Set output mode */ ret = csi_gpio_pin_dir(&pin, GPIO_DIRECTION_OUTPUT); GPIO_CHECK_RETURN(ret); } // EOF switch
3.4 串口处理部分
// uart variable static csi_uart_t g_uart; static volatile uint8_t rx_async_flag = 0; static volatile uint8_t tx_async_flag = 0; static uint8_t tx_buffer[140]; static uint8_t rx_buffer[140]; int recieve_data_num = 37; #define DATE_UART_BAUDRATE 4800 #define DATE_UART_IDX 5 #define UART_CHECK_RETURN(ret) do { if (ret != CSI_OK) { return -1; } } while(0); // crc function unsigned int calc_crc(unsigned char crcbuf, unsigned int crc); unsigned int chk_crc(unsigned char* buf, unsigned char len); //task static aos_task_t task_date_uart5; void date_uart5_entry() { while(1) { switch(recv_cmd){ case 0: break; case 1: csi_gpio_pin_write(&pin, GPIO_PIN_HIGH); recv_cmd = 0; break; case 2: csi_gpio_pin_write(&pin, GPIO_PIN_LOW); recv_cmd = 0; break; case 3: read_consumer_data(); recv_cmd = 0; break; default: break; }//eof switch aos_msleep(2000); }//eof while }//eof func //callback function of uart static void uart_event_cb(csi_uart_t *uart, csi_uart_event_t event, void *arg) { switch (event) { case UART_EVENT_SEND_COMPLETE: tx_async_flag = 1; break; case UART_EVENT_RECEIVE_COMPLETE: rx_async_flag = 1; break; default: break; }//eof switch }//eof func void uart_init() { csi_pin_set_mux(PB4, PB4_UART5_TX); csi_pin_set_mux(PB5, PB5_UART5_RX); /* init uart, DATE_UART_IDX == 5 */ ret = csi_uart_init(&g_uart, DATE_UART_IDX); UART_CHECK_RETURN(ret); /* set uart baudrate */ ret = csi_uart_baud(&g_uart, DATE_UART_BAUDRATE); UART_CHECK_RETURN(ret); /* set uart format */ ret = csi_uart_format(&g_uart, UART_DATA_BITS_8, UART_PARITY_NONE, UART_STOP_BITS_1); UART_CHECK_RETURN(ret); /* attach callback to uart device */ ret = csi_uart_attach_callback(&g_uart, uart_event_cb, NULL); UART_CHECK_RETURN(ret); } // calculate crc function unsigned int calc_crc(unsigned char crcbuf, unsigned int crc) { unsigned char i; unsigned char chk; crc = crc ^ crcbuf; for (i = 0; i < 8; i++) { chk = (unsigned char)(crc & 1); crc = crc >> 1; crc = crc & 0x7fff; if (chk == 1) crc = crc ^ 0xa001; crc = crc & 0xffff; } return crc; } // verify crc function unsigned int chk_crc(unsigned char* buf, unsigned char len) { unsigned char hi, lo; unsigned int i; unsigned int crc; crc = 0xFFFF; for (i = 0; i < len; i++) { crc = calc_crc(*buf, crc); buf++; } hi = (unsigned char)(crc % 256); lo = (unsigned char)(crc / 256); crc = (((unsigned int)(hi)) << 8) | lo; return crc; }
3.5 主函数
int main(void) { cxx_system_init(); board_yoc_init(); switch_init(); uart_init(); /* Subscribe */ event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL); event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL); aos_task_new_ext(&task_date_uart5, "task_date_uart5", date_uart5_entry, NULL, 4096, AOS_DEFAULT_APP_PRI); // aos_task_new_ext(&uart5_proc, "uart5_proc", data_proc, NULL, 1024, 30); app_wifi_init(); // while (1) { // if (is_mqtt_ready == 1) { // // read_consumer_data(); // printf("OK"); // // aos_msleep(1000); // } // // test_uart(); // // aos_msleep(2000); // } }
04 问题汇总
uart的配置
在官方的GitBook中对驱动函数进行了详细地讲解并附有相关例程:文档首页 · GitBook (t-head.cn)
但并未对具体的底层配置修改进行说明,在一开始编写串口部分的代码时,一直未能成功初始化并调通串口,在工程师的帮助之下对D1 dock pro的底层配置有了一定的了解。这里以led_demo为例,演示如何在此基础之上成功配置uart5。
改动1
改动2
改动3
改动4
做完以上三处改动,即可参考UART · GitBook (t-head.cn)中的使用示例对uart5进行验证,注意需要先 csi_pin_set_mux(PB4, PB4_UART5_TX); 和 csi_pin_set_mux(PB5, PB5_UART5_RX);
05 项目总结
“我们对‘碳中和’比较感兴趣,学校也鼓励我们探索交叉学科,我负责系统架构搭建和区块链技术,另外两位队员负责硬件编程及网页编程。从最初简单的能源物联网演示,到利用区块链技术实现M2M自主交易,我们做了很多讨论和尝试,终于在RISC-V平台上跑通了程序!”“萌新队”队长、华东师范大学大四学生龚丹妮说。
审核编辑 :李倩
全部0条评论
快来发表一下你的评论吧 !