Commit 5f93f716 authored by Alexander Alashkin's avatar Alexander Alashkin Committed by Cesanta Bot

Add STM32F4/CC3100 example & Co

PUBLISHED_FROM=7f805d89dcb795c9b3ee637ef1dbdcfcf3323469
parent 36405545
......@@ -3,7 +3,7 @@
# `wildcard ./*/` works in both linux and linux/wine, while `wildcard */` enumerates nothing under wine
SUBDIRS = $(sort $(dir $(wildcard ./*/)))
SUBDIRS:=$(filter-out ./ ./CC3200/ ./ESP8266_RTOS/ ./MSP432/, $(SUBDIRS))
SUBDIRS:=$(filter-out ./ ./CC3200/ ./ESP8266_RTOS/ ./MSP432/ ./STM32F4_CC3100/, $(SUBDIRS))
ifeq ($(OS), Windows_NT)
SUBDIRS:=$(filter-out ./load_balancer/ ./netcat/ ./raspberry_pi_mjpeg_led/ ./captive_dns_server/, $(SUBDIRS))
......
SDK ?= $(shell cat sdk.version)
SRC_DIR ?= $(realpath ../../..)
.PHONY: all clean
MAKEFLAGS += w
all clean:
docker run --rm -i -v $(SRC_DIR):/src $(SDK) \
/bin/bash -c "\
make -C /src/mongoose mongoose.c mongoose.h && \
make -C /src/mongoose/examples/STM32F4_CC3100 -f Makefile.build $@ -$(MAKEFLAGS) \
"
.PHONY: all clean
CC3100_DRV_SRCS = driver.c device.c nonos.c socket.c netapp.c wlan.c
CC3100_PLATFORM_SRCS = spi.c board.c cli_uart.c stm32f4xx_it.c system_stm32f4xx.c stm32f4xx_hal_msp.c
STM32F4_SRCS = stm32f4xx_hal_spi.c stm32f4xx_hal_gpio.c stm32f4xx_hal_cortex.c\
stm32f4xx_hal.c stm32f4xx_hal_rcc.c stm32f4xx_hal_dma.c \
stm32f4xx_hal_pwr_ex.c stm32f4_discovery.c stm32f4xx_hal_i2c.c \
stm32f4xx_hal_uart.c stm32f4xx_hal_tim.c \
stm32f4xx_hal_tim_ex.c stm32f4xx_hal_rtc_ex.c stm32f4xx_hal_rtc.c
SRCS = main.c mongoose.c startup_utils.c ${CC3100_DRV_SRCS} ${CC3100_PLATFORM_SRCS} ${STM32F4_SRCS}
REPO_ROOT=./../../../
CC3100_SDKROOT=/opt/CC3100SDK_1.2.0/cc3100-sdk
STM32CUBEF4_ROOT=/opt/STM32CubeF4
STM32CUBEF4_DRV_PATH=${STM32CUBEF4_ROOT}/Drivers
CC = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
FP_FLAGS = -mfloat-abi=hard -mfpu=fpv4-sp-d16
ARCH_FLAGS = -mthumb -mcpu=cortex-m4
BUILD_DIR=.build
OUT_DIR=out
PROJECT=example
BOARD=STM32F429xx
STARTUP_SCRIPT=startup_stm32f429xx.s
INCDIRS = $(addprefix -I,$(IPATH))
OBJS = $(addprefix $(BUILD_DIR)/,$(patsubst %.c,%.o,$(SRCS)) $(patsubst %.s,%.o,$(STARTUP_SCRIPT)))
VPATH = ${CC3100_SDKROOT}/simplelink/source\
${CC3100_SDKROOT}/platform/stm32discovery\
${STM32CUBEF4_ROOT}/Drivers/STM32F4xx_HAL_Driver/Src\
${STM32CUBEF4_ROOT}/Drivers/BSP/STM32F4-Discovery\
${REPO_ROOT}/mongoose
# CC3100 SDK and STM32 SDK include headers w/out path, just like
# #include "simplelink.h". As result, we have to add all required directories
# into IPATH
IPATH = . \
${CC3100_SDKROOT}/simplelink/include\
${CC3100_SDKROOT}/platform/stm32discovery\
${STM32CUBEF4_DRV_PATH}/BSP/STM32F4-Discovery\
${STM32CUBEF4_DRV_PATH}/STM32F4xx_HAL_Driver/Inc\
${STM32CUBEF4_DRV_PATH}/CMSIS/Include\
${STM32CUBEF4_DRV_PATH}/CMSIS/Device/ST/STM32F4xx/Include
LDFLAGS = --static -nostartfiles
LDLIBS = -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
LDSCRIPT = stm32f429xx.ld
INCDIRS = $(addprefix -I,$(IPATH))
CFLAGS = ${FP_FLAGS} ${ARCH_FLAGS} ${INCDIRS}\
-D${BOARD} -DEXT_LIB_REGISTERED_GENERAL_EVENTS \
-DSL_FULL\
-DCS_PLATFORM=6\
-D__STM32F407xx_H\
-DMG_SIMPLELINK_NO_OSI \
-g -fno-common -ffunction-sections -fdata-sections \
ELF = ${BUILD_DIR}/${PROJECT}.elf
BIN = ${OUT_DIR}/${PROJECT}.bin
all: ${BUILD_DIR} ${OUT_DIR} ${OBJS} ${ELF} ${BIN}
${BUILD_DIR}:
mkdir -p $@
${OUT_DIR}:
mkdir -p $@
$(BUILD_DIR)/%.o: %.c
$(CC) ${CFLAGS} $^ -c -o $@
$(BUILD_DIR)/%.o: %.s
$(CC) ${CFLAGS} $^ -c -o $@
${ELF}: ${OBJS}
$(CC) ${ARCH_FLAGS} ${FP_FLAGS} $(LDFLAGS) $(OBJS) $(LDLIBS) -T$(LDSCRIPT) -o $@
${BIN}: ${ELF}
$(OBJCOPY) -Obinary $^ $@
clean:
rm -rf ${BUILD_DIR} ${OUT_DIR}
# STM32F4 example project
This example shows how to use mongoose on STM32 boards.
To run it you will need:
- [STM32F429-Discovery](http://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-eval-tools/stm32-mcu-eval-tools/stm32-mcu-discovery-kits/32f429idiscovery.html) dev board
- [CC3100](http://www.ti.com/product/CC3100) WiFi network processor
## Wiring scheme
By default, example uses SPI4 for communication with CC3100 and UART1 for
the debug output. All parameters are described in file `stm32f4xx_hal_msp.h`,
they can be changed to use another SPI and/or UART
To use default scheme connect (CC3100 -> STM32-DISCO) connect:
```
DO -> PE5
DIN -> PE6
CLK -> PE2
CS -> PB12
IRQ -> PA0
HIB -> PB0
```
## Building firmware
Change `user_params.h`, put correct WiFi SSID and password there,
also change `MQTT_BROKER_ADDRESS` to the real broker address.
`make` in `mongoose/STM32F4_CC3100` will download required docker image and make
firmware. Result will be in `STM32F4_CC3100/out` folder.
## Uploading firmare
Uploading firmware method depends on how you connected STM32 board to your
computer. If it is connected via USB ST-LINK connected it is appears as
a flash drive and in order to upload firmware just copy `out/example.bin`
to that drive.
## Running
Compile two additional samples: `mqtt_broker` and `mqtt_client` and run them
in different terminals.
Press `reset` (or repower) STM board.
The board will connect to broker and will start to publish its uptime in
`/stuff` channel. `mqtt_client` is subscribed on this channel as well, so
it should start to print
```
Got incoming message /stuff: Current tick: 99000
Forwarding to /test
Got incoming message /stuff: Current tick: 100120
Forwarding to /test
Got incoming message /stuff: Current tick: 101250
Forwarding to /test
...
```
If you connect UART to serial port monitor (pin PA9 or ST-LINK device, like /dev/ttyACM0)
you should see device output:
```
**** Hello ****
Initializing CC3100 on SPI4
Starting WiFi connect
Done, waiting for events
Connected to WiFi
Got IP
Connected to 192.168.1.108:1883
Connected to broker
Subscribing to /test
Subscription acknowledged
Publishing message with tick=1
Got incoming message /test: Current tick: 4487
Publishing message with tick=2
Got incoming message /test: Current tick: 5597
Publishing message with tick=3
Got incoming message /test: Current tick: 6717
...
```
This output looks like this because the device sends messages `Current tick: ....`
into `/stuff` channel, `mqtt_client` receives all messages in this channel
and sends them to `/test` channel. But the device is subscribed to this channel
so, it receives it back.
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#include "./../../mongoose.h"
#include "stm32f4xx_hal_msp.h"
#include "stm32f4xx.h"
#include "user_params.h"
enum SampleState { ssIniting, ssConnectedWLAN, ssWorking, ssStopped };
static enum SampleState state = ssIniting;
static struct mg_mgr mgr;
static int msg_id;
void Error_Handler() {
/* Turn LED4 (red) on/off */
while (1) {
BSP_LED_Toggle(LED4);
Delay(100);
}
}
void SimpleLinkWlanEventHandler(SlWlanEvent_t *pWlanEvent) {
switch (pWlanEvent->Event) {
case SL_WLAN_CONNECT_EVENT:
CLI_Write("Connected to WiFi\r\n");
break;
case SL_WLAN_DISCONNECT_EVENT:
CLI_Write("Disconnected WiFi\r\n");
break;
default:
CLI_Write("Got Wlan event %d\r\n", pWlanEvent->Event);
break;
}
}
void SimpleLinkNetAppEventHandler(SlNetAppEvent_t *pNetAppEvent) {
switch (pNetAppEvent->Event) {
case SL_NETAPP_IPV4_IPACQUIRED_EVENT:
CLI_Write("Got IP\r\n");
state = ssConnectedWLAN;
break;
default:
CLI_Write("Got NetApp Event: %d\r\n", pNetAppEvent->Event);
break;
}
}
struct mg_mqtt_topic_expression topic_expressions[] = {{"/test", 0}};
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p;
switch (ev) {
case MG_EV_CONNECT:
if (*(int *) p != 0) {
CLI_Write("Failed to connect to %s\r\n", MQTT_BROKER_ADDRESS);
} else {
CLI_Write("Connected to %s\r\n", MQTT_BROKER_ADDRESS);
}
struct mg_send_mqtt_handshake_opts opts;
memset(&opts, 0, sizeof(opts));
opts.user_name = MQTT_USER_NAME;
opts.password = MQTT_USER_PWD;
mg_set_protocol_mqtt(nc);
mg_send_mqtt_handshake_opt(nc, "STM32", opts);
break;
case MG_EV_MQTT_CONNACK:
if (msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED) {
CLI_Write("Got mqtt connection error %d\n\r", msg->connack_ret_code);
} else {
CLI_Write("Connected to broker\n\r");
}
CLI_Write("Subscribing to /test\n\r");
mg_mqtt_subscribe(nc, topic_expressions,
sizeof(topic_expressions) / sizeof(*topic_expressions),
++msg_id);
break;
case MG_EV_MQTT_SUBACK:
CLI_Write("Subscription acknowledged\r\n");
state = ssWorking;
break;
case MG_EV_MQTT_PUBLISH:
CLI_Write("Got incoming message %s: %.*s\r\n", msg->topic,
(int) msg->payload.len, msg->payload.p);
break;
case MG_EV_POLL:
if (state == ssWorking) {
char msg[100];
uint32_t tick = HAL_GetTick();
int len = snprintf(msg, sizeof(msg), "Current tick: %u", tick);
CLI_Write("Publishing message with tick=%u\r\n", tick);
mg_mqtt_publish(nc, "/stuff", ++msg_id, MG_MQTT_QOS(0), msg, len);
}
break;
case MG_EV_CLOSE:
CLI_Write("Connection to broker is closed\r\n");
state = ssStopped;
break;
default:
break;
}
}
int main(void) {
stopWDT();
initClk();
BSP_LED_Init(LED3);
BSP_LED_Init(LED4);
CLI_Configure();
CLI_Write("\n\n\n**** Hello ****\r\n");
CLI_Write("Initializing CC3100 on SPI%d\r\n", SPIx_NUMBER);
int ret = sl_Start(NULL, NULL, NULL);
SlSecParams_t sec_params;
memset(&sec_params, 0, sizeof(sec_params));
sec_params.Key = NET_PWD;
sec_params.KeyLen = sizeof(NET_PWD) - 1;
sec_params.Type = NET_SECURITY;
CLI_Write("Starting WiFi connect\r\n");
ret = sl_WlanConnect(NET_SSID, sizeof(NET_SSID) - 1, 0, &sec_params, NULL);
CLI_Write("Done, waiting for events\n\r");
while (1) {
BSP_LED_Toggle(LED3);
_SlNonOsMainLoopTask();
if (state == 1) {
mg_mgr_init(&mgr, NULL);
if (mg_connect(&mgr, MQTT_BROKER_ADDRESS, ev_handler) == NULL) {
CLI_Write("Failed to create connection\n\r");
}
state = 2;
}
mg_mgr_poll(&mgr, 1000);
}
}
void HAL_UART_ErrorCallback(UART_HandleTypeDef *UartHandle) {
/* Turn LED4 (red) on: Transfer error in reception/transmission process */
BSP_LED_On(LED4);
}
docker.cesanta.com/stm32-cc3100-build:stm32-1.10.0_cc3100-1.2.0-r1
This diff is collapsed.
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#include <stdlib.h>
/*
* STM's startup script requires __libc_init_array for successfull
* initialization.
* Oficially supported IDE (Keil, IAR...) provide this function.
* We use our own
*/
extern void (*__preinit_array_start[])(void) __attribute__((weak));
extern void (*__preinit_array_end[])(void) __attribute__((weak));
extern void (*__init_array_start[])(void) __attribute__((weak));
extern void (*__init_array_end[])(void) __attribute__((weak));
void __libc_init_array(void) {
size_t count;
size_t i;
count = __preinit_array_end - __preinit_array_start;
for (i = 0; i < count; i++) __preinit_array_start[i]();
count = __init_array_end - __init_array_start;
for (i = 0; i < count; i++) __init_array_start[i]();
}
/*
* Copyright (c) 2016 Cesanta Software Limited
* All rights reserved
* GCC linker script for stm32f429. Based on startup sctipt
* (startup_stm32f429xx.s - COPYRIGHT 2015 STMicroelectronics)
*/
ENTRY(Reset_Handler)
_estack = 0x2002FFFF; /* required by startup sctipt */
MEMORY
{
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
}
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
*(.isr_vector)
. = ALIGN(4);
} >FLASH
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7t)
*(.eh_frame)
*(.init)
*(.fini)
. = ALIGN(4);
_etext = .;
} >FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
_sidata = LOADADDR(.data); /* required by startup sctipt */
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} >RAM AT> FLASH
_siccmram = LOADADDR(.ccmram); /* required by startup sctipt */
.ccmram :
{
. = ALIGN(4);
_sccmram = .;
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .;
} >CCMRAM AT> FLASH
. = ALIGN(4);
.bss :
{
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >RAM
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
. = ALIGN(4);
} >RAM
}
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#ifndef CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_STM32F4XX_HAL_MSP_H_
#define CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_STM32F4XX_HAL_MSP_H_
/*
* Definitions for SPI used to communicate with CC3100
* All samples in STMCubeF4 use SPI4 for examples
* We do the same. See README.MD for details.
*/
#define SPIx_NUMBER 4
#define SPIx SPI4
#define SPIx_CLK_ENABLE() __HAL_RCC_SPI4_CLK_ENABLE()
#define SPIx_SCK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define SPIx_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define SPIx_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define SPIx_NSS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define SPIx_FORCE_RESET() __HAL_RCC_SPI4_FORCE_RESET()
#define SPIx_RELEASE_RESET() __HAL_RCC_SPI4_RELEASE_RESET()
#define SPIx_SCK_PIN GPIO_PIN_2
#define SPIx_SCK_GPIO_PORT GPIOE
#define SPIx_SCK_AF GPIO_AF5_SPI4
#define SPIx_MISO_PIN GPIO_PIN_5
#define SPIx_MISO_GPIO_PORT GPIOE
#define SPIx_MISO_AF GPIO_AF5_SPI4
#define SPIx_MOSI_PIN GPIO_PIN_6
#define SPIx_MOSI_GPIO_PORT GPIOE
#define SPIx_MOSI_AF GPIO_AF5_SPI4
#define SPIx_IRQn SPI4_IRQn
#define SPIx_IRQHandler SPI4_IRQHandler
#define SPI_CS_PIN GPIO_PIN_12
#define SPI_CS_PORT GPIOB
/*
* CC3100 requires 2 additional pins for communication
* See http://processors.wiki.ti.com/index.php/CC31xx_SPI_Host_Interface
* for details
*/
#define MCU_IRQ_PIN GPIO_PIN_0
#define MCU_IRQ_PORT GPIOA
#define MCU_nHIB_PORT GPIOB
#define MCU_nHIB_PIN GPIO_PIN_0
/*
* Definitions used for debug uart.
* By default we use USART1, and this allows to connect serial port monitor
* to dedicated pin (PA9) and to ST-LINK port (/dev/ttyACM0 in Linux)
* See README.MD for details.
*/
#define USARTx USART1
#define USART_SPEED 115200
#define USARTx_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE();
#define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USARTx_FORCE_RESET() __HAL_RCC_USART1_FORCE_RESET()
#define USARTx_RELEASE_RESET() __HAL_RCC_USART1_RELEASE_RESET()
#define USARTx_TX_PIN GPIO_PIN_9
#define USARTx_TX_GPIO_PORT GPIOA
#define USARTx_TX_AF GPIO_AF7_USART1
#define USARTx_RX_PIN GPIO_PIN_10
#define USARTx_RX_GPIO_PORT GPIOA
#define USARTx_RX_AF GPIO_AF7_USART1
#endif /* CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_STM32F4XX_HAL_MSP_H_ */
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#ifndef CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_USER_PARAMS_H_
#define CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_USER_PARAMS_H_
/* Put you WIFI parameters here */
#define NET_SSID "my_ssid"
#define NET_PWD "my_password"
#define NET_SECURITY SL_SEC_TYPE_WPA_WPA2
#define MQTT_BROKER_ADDRESS "192.168.1.108:1883"
#define MQTT_USER_NAME NULL
#define MQTT_USER_PWD NULL
#endif /* CS_MONGOOSE_EXAMPLES_STM32F4_CC3100_USER_PARAMS_H_ */
......@@ -32,6 +32,8 @@ int main(void) {
}
nc->user_data = &brk;
printf("MQTT broker started on %s\n", address);
/*
* TODO: Add a HTTP status page that shows current sessions
* and subscriptions
......
......@@ -10440,8 +10440,6 @@ int sl_fs_init() {
/* Amalgamated: #include "common/platform.h" */
#include <simplelink/include/netapp.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
int res;
struct in_addr *in = (struct in_addr *) src;
......@@ -10481,7 +10479,7 @@ int inet_pton(int af, const char *src, void *dst) {
#ifdef MG_MODULE_LINES
#line 1 "./src/../../common/platforms/simplelink/sl_mg_task.c"
#endif
#if defined(MG_SOCKET_SIMPLELINK)
#if defined(MG_SOCKET_SIMPLELINK) && !defined(MG_SIMPLELINK_NO_OSI)
/* Amalgamated: #include "mg_task.h" */
......
......@@ -76,6 +76,7 @@
#define CS_P_ESP_LWIP 3
#define CS_P_CC3200 4
#define CS_P_MSP432 5
#define CS_P_CC3100 6
/* If not specified explicitly, we guess platform by defines. */
#ifndef CS_PLATFORM
......@@ -405,6 +406,57 @@ unsigned long os_random(void);
* All rights reserved
*/
#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_
#define CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_
#if CS_PLATFORM == CS_P_CC3100
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#define MG_SOCKET_SIMPLELINK 1
#define MG_DISABLE_SOCKETPAIR 1
#define MG_DISABLE_SYNC_RESOLVER 1
#define MG_DISABLE_POPEN 1
#define MG_DISABLE_CGI 1
#define MG_DISABLE_DAV 1
#define MG_DISABLE_DIRECTORY_LISTING 1
#define MG_DISABLE_FILESYSTEM 1
/*
* CC3100 SDK and STM32 SDK include headers w/out path, just like
* #include "simplelink.h". As result, we have to add all required directories
* into Makefile IPATH and do the same thing (include w/out path)
*/
#include <simplelink.h>
#include <netapp.h>
typedef int sock_t;
#define INVALID_SOCKET (-1)
#define to64(x) strtoll(x, NULL, 10)
#define INT64_FMT PRId64
#define INT64_X_FMT PRIx64
#define SIZE_T_FMT "u"
#define SOMAXCONN 8
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
int inet_pton(int af, const char *src, void *dst);
#endif /* CS_PLATFORM == CS_P_CC3100 */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_ */
/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
#define CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
#if CS_PLATFORM == CS_P_CC3200
......@@ -657,6 +709,7 @@ int _stat(const char *pathname, struct stat *st);
#undef SL_INC_STD_BSD_API_NAMING
#include <simplelink/include/simplelink.h>
#include <simplelink/include/netapp.h>
/* Now define only the subset of the BSD API that we use.
* Notably, close(), read() and write() are not defined. */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment