$5 ESP32C3/$3 ESP32S2 I2C to ASCII Serial USB Convertor

I occaisonnaly need to connect physical worlds with logical. When I do there always seems to be a leap I must make in directly controlling hardware pins from a rack full of Dell servers. The I2C boards on ebay (CH341 or something) require odd drivers in kernels Id rather not mess with. So for $5 I figured a XIAO ESP32C3 module that looks like /dev/ttyACM0 on one ends and I2C or whatever on the other was a quick fix. Since com is plain ASCII and ASCII encoded hex, it is not fast but will pass through most channels. Example: "i2c w 27 00010203" writes 4 bytes 00010203 (hex) to I2C device at 27 hex. Similarly "i2c r 27 16" reads 16 bytes from I2C device at 27 hex. Here's the ESP32C3 and sample Linux source (tried to strip out my custom code for clarity). PS: Sure my personal version offloads a bunch of other I/O from the host - why not, the ESP32 is overkill for this application - but I leave you to your own devices and my sample code.
UPDATE: Now for a $3 (actually $2.87) ESP32S2 module (right picture) you can have the same functionality. Yay!
UPDATE: Since ESP32-S2 has 2 CDC-/dev/ttyACM, made /dev/ttyACM1 passthru to UART0 (TX=IO1, RX=IO3). You can set baud of UART0 w/ new "ser b 115200" command via /dev/ttyACM0 making this a $2.87 USB-to-Serail converter as well. I assume you will modify. FWIW firmware binary for S2 here i2c2ser.bin with hash = 93f9af02c9fbbd8ad9f0985d24198ee519f65bcee480f77cbd8edd7ee9787067

ESP32S2/ESP32C3 source i2c2serpub.c and sample Linux host source to communicate with it sampleserio.c
i2c2serpub.c
/*
 * Copyright (C) 2023 Department C Inc., All rights reserved.
 *
 * ESP32C3/S2 "Serial I2C Convertor"
 * R H Lamb 2023
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL DEPARTMENT C INC BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * How to build:
 cd ~/esp32/i2c2ser  (this code in main/ subdir)
 pushd ~/esp32/esp-idf
 ./install.sh esp32s2  or esp32c3
 popd
 rm -rf build main/idf_component.yml
 . ~/esp32/esp-idf/export.sh
 idf.py set-target esp32s2  or esp32c3
 IF esp32s2
 idf.py add-dependency esp_tinyusb
 FI
 idf.py menuconfig
 Component config ->
   FreeRTOS -> Kernel -> configUSE_TRACE_FACILITY
 IF esp32s2
   ESP PSRAM -> Support for external, SPI-connected RAM -> SPI RAM config ->
     SPI RAM access method -> Make RAM allocatable using malloc() as well  
   TinyUSB Stack -> Communication Device Class (CDC) -> CDC Channel Count -> 2
 FI
 Save, exit
 idf.py build
 IF esp32s2
 While pressing "0" button, push and release "RST" button
 FI
 Replace PORT with /dev/ttyACM0
 IF esp32s2
 Press and release "RST" button
 FI
 screen /dev/ttyACM0
 enter "stat"
 *
 */

// Pick ONLY one
//#define ESP32C3
#define ESP32S2

#define THISVERSION "V0.003"
#define FINDABLEVERSION THISVERSION" Ricks i2c2ser dc.org (c) Dept C Inc 2023"

#pragma GCC diagnostic ignored "-Wformat"
#pragma GCC diagnostic ignored "-Wmissing-braces"

static void rlprintf(const char *format, ...);
#define myprintf rlprintf
#define __IO  volatile

#include 
#include 
#include 
#include 
#include 
#include 

#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_mac.h"
#include "esp_sleep.h"
#include "esp_wifi.h"
#include "esp_task_wdt.h"
#include "esp_chip_info.h"
#include "esp_flash.h"

static time_t mystarttime=0;
static uint8_t mymac[6];
#define min(x, y) ( ((x)<(y))?(x):(y) )

static void delay(uint32_t t)
{
  vTaskDelay( (t/portTICK_PERIOD_MS) );
}

/******************************************************************************
 * my mutex - matters if you add another worker thread
 ******************************************************************************/
static SemaphoreHandle_t xSemaphoreI2C,xSemaphoreM;
static int _mymutex(SemaphoreHandle_t *sh,const char *fnc,uint8_t sr)
{
  if(*sh == NULL) {
    if((*sh=xSemaphoreCreateMutex()) == NULL) {
      myprintf("%s: cannot create mutex\r\n",fnc);
      return -1;
    }
  }
  if(sr) {
    if(xSemaphoreTake(*sh,10) == pdTRUE) {
      return 0;
    } else {
      return -1;
    }
  } else {
    xSemaphoreGive(*sh);
  }
  return 0;
}
#define mymutexM(b) _mymutex(&xSemaphoreM,__func__,b)
#define mymutexI2C(b) _mymutex(&xSemaphoreI2C,__func__,b)

/******************************************************************************
 * your malloc
 ******************************************************************************/
static uint32_t memmax=0,memtot=0;
//#define USEMYMALLOC
#ifdef USEMYMALLOC
// we all have our own heap allocators to catch bugs. Put yours here.
#else // USEMYMALLOC
#define mymalloc malloc
#define myfree free
#endif // USEMYMALLOC

/******************************************************************************
 * my "printf"
 ******************************************************************************/
static void cdc0_write(uint8_t *p,int n);
static uint8_t rlprintf_buffer[1024];
static void rlprintf(const char *format, ...)
{
  va_list args;
  int n,i;
  uint8_t *p;
  static uint8_t inuse=0;

  while(inuse) delay(1); // yield(); // block other tasks trying to use this and its buffer
  inuse = 1;

  p = rlprintf_buffer;
  i = sizeof(rlprintf_buffer) - 1;

  va_start(args,format);
  n = vsnprintf((char *)p,i,format,args);
  p[n] = 0; // term string in case its ASCIIZ
  va_end(args);
  cdc0_write(rlprintf_buffer,n);
  inuse = 0;
}

/******************************************************************************
 * your Time function
 ******************************************************************************/
static time_t mytime(time_t *x)
{
  return time(x); // returns 64bit.  careful elsewhere
}

/******************************************************************************
 * my ancient support libs
 ******************************************************************************/
static int hex2i(char c)
{
  if(c >= '0' && c <= '9') return (int)(c - '0');
  if(c >= 'A' && c <= 'F') return (int)((c - 'A') + 10);
  if(c >= 'a' && c <= 'f') return (int)((c - 'a') + 10);
  return -1;
}
static int ahex2bin(uint8_t *out,char *in)
{
  uint8_t *p;
  int i,j;
  p = out;
  while(*in) {
    i = hex2i(in[0]);
    
    if(in[1]) j = hex2i(in[1]); else j = 0;
    
    if(i < 0 || j < 0) return -1;
    //in += 2;
    *p++ = (uint8_t) ((i<<4)|j); // make sure inplace ahex 2 bin works
    
    if(in[1]) in += 2; else in++;
    
  }
  return (int)(p - out);
}
static int bin2ahex(char *out,uint8_t *in,int n)
{
  int i;
  char *p=out;
  static char htoas[]="0123456789ABCDEF";
  for(i=0;i> 4];
    *p++ = htoas[in[i]&0x0f];
  }
  *p++ = '\0';
  return (int)(p - out);
}
static int rdump(uint8_t *ptr,int n)
{
  int i,j1,j2; char buf[80]; static char htoas[]="0123456789ABCDEF";
  j1 = j2 = 0; /* gcc -W */
  for(i=0;i> 4]; buf[j1+1] = htoas[ptr[i]&0x0f];
    if(ptr[i] >= 0x20 && ptr[i] < 0x80) buf[j2]=ptr[i]; else buf[j2]='.';
  }
  buf[j2]='\0'; myprintf("%s|\r\n",buf);
  return 0;
}
static int adump(uint8_t *ptr,int n)
{
  int i;
  for(i=0;i>1,n,ret);
    return 0;
  }
  return n;
}
static int myi2cwrite(uint8_t addr,uint8_t *p,int n,int tmo)
{
  int ret;
  i2c_cmd_handle_t cmd = i2c_cmd_link_create();
  addr <<= 1;
  i2c_master_start(cmd);  
  i2c_master_write_byte(cmd,addr,1);  
  i2c_master_write(cmd,p,n,1);
  i2c_master_stop(cmd);
  ret = i2c_master_cmd_begin(I2C_NUM_0,cmd,1000/portTICK_PERIOD_MS); // portTICK_RATE_MS);
  i2c_cmd_link_delete(cmd);
  if(ret) {
    myprintf("%s: %02X %d failed %d\r\n",__func__,addr>>1,n,ret);
    return 0;
  }
  return n;
}


/*****************************************************************************
 * UART 0
 ****************************************************************************/
#include "driver/uart.h"
#define UART0BAUD 460800
static int myuart0_bad=1;
static void myuart0_init()
{
  uart_config_t uart_config = {
    .baud_rate = UART0BAUD,
    .data_bits = UART_DATA_8_BITS,
    .parity    = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
  };
  if(uart_param_config(UART_NUM_0,&uart_config)) return;
  if(uart_driver_install(UART_NUM_0,256,0,0,NULL,0)) return; // (uart,rxbuflen,txbuflen,qsize,queue,0);
  if(uart_set_pin(UART_NUM_0,1,3,-1,-1)) return; // tx,rx,rts,cts
  myuart0_bad = 0;
}
static int myuart0_setbaud(int baud)
{
  return uart_set_baudrate(UART_NUM_0,baud);
}
static int myuart0_write(uint8_t *p,int n,int tmo)
{
  int i=uart_write_bytes(UART_NUM_0,(const char*)p,n); // blocks?
  uart_wait_tx_done(UART_NUM_0,tmo);
  return i;
}
static int myuart0_read(uint8_t *p,int n,int tmo)
{
  return uart_read_bytes(UART_NUM_0,p,n,tmo); // tmo=ticks
}


/******************************************************************************
 * Stats
 ******************************************************************************/
#include "esp_psram.h"

static int mystats(int argc,char *argv[])
{
  char lbuf[128];

  myprintf("%s\r\n",FINDABLEVERSION);
  {
    esp_chip_info_t chip_info;
    uint32_t flash_size;
    esp_chip_info(&chip_info);
    myprintf("%s chip. %d CPU core(s), WiFi%s%s%s, ",
	     CONFIG_IDF_TARGET,
	     chip_info.cores,
	     (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
	     (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "",
	     (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
    unsigned major_rev = chip_info.revision / 100;
    unsigned minor_rev = chip_info.revision % 100;
    myprintf("silicon revision v%d.%d, \r\n", major_rev, minor_rev);
    if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
      myprintf(" Get flash size failed\r\n");
    }
    myprintf("%" PRIu32 "MB %s flash. %s",
	     flash_size/(uint32_t)(1024 * 1024),
	     (chip_info.features&CHIP_FEATURE_EMB_FLASH)?"embedded":"external",
	     (chip_info.features&CHIP_FEATURE_EMB_PSRAM)?" embedded psram.":"");
    myprintf("Minimum free heap size: %" PRIu32 " bytes.",esp_get_minimum_free_heap_size());
    myprintf("\r\n");
#ifdef ESP32S2    
    myprintf("psram: %d bytes\r\n",esp_psram_get_size());
#endif // ESP32S2    
  }
  
  uint32_t rs = (uint32_t)(mytime(NULL) - mystarttime);
  if(rs > 86400) sprintf(lbuf,"%d:days %d:hours",(rs/86400),(rs%86400)/3600);
  else sprintf(lbuf,"%d:hrs %d:mins",rs/3600,(rs%3600)/60);  
  myprintf("mac:%02X:%02X:%02X:%02X:%02X:%02X ",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
  myprintf("Uptime:%s \r\n",lbuf);  
  TaskStatus_t xTaskDetails;
  memset(&xTaskDetails,0,sizeof(TaskStatus_t));
  
  vTaskGetInfo(NULL,&xTaskDetails,pdTRUE,eInvalid); // cpu intensive
  
  myprintf(" stackmin:%d heap:%d/%d(min) mymemused:%d/%d(max)\r\n",
           xTaskDetails.usStackHighWaterMark*sizeof(configSTACK_DEPTH_TYPE),
           esp_get_free_heap_size(),
           esp_get_minimum_free_heap_size(),
           memtot,memmax);
#define DOTASKLIST
#ifdef DOTASKLIST  
  {
    unsigned long ulTotalRunTime;
    int i,j,k,xi=uxTaskGetNumberOfTasks();
    TaskStatus_t *xp=(TaskStatus_t *)mymalloc(xi*sizeof(TaskStatus_t));
    xi = uxTaskGetSystemState(xp,xi,&ulTotalRunTime );
    myprintf("%d tasks (name stack-bytes-unused pri num)\r\n",xi);
    for(i=0;ipcTaskName); lbuf[j+k] = ' '; j = 10;
      k = sprintf(&lbuf[j]," %d",ts->usStackHighWaterMark * sizeof(configSTACK_DEPTH_TYPE)); lbuf[j+k] = ' '; j +=6; // space+str(10)
      k = sprintf(&lbuf[j]," %d",ts->uxCurrentPriority); lbuf[j+k] = ' '; j += 3; // space + 2digit
      k = sprintf(&lbuf[j]," %d",ts->xTaskNumber); lbuf[j+k] = ' ';
      lbuf[j+k] = '\0';
      myprintf("%s\r\n",lbuf);
    }
    myfree(xp);
  }
#endif // DOTASKLIST
  return 0;
}

/******************************************************************************
 * my ancient cmd parser
 ******************************************************************************/
static int dosleep(int argc,char *argv[])
{
  int n = 6;
  if(argc > 1) n = atoi(argv[1]);
  ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(n*1000*1000));
  esp_deep_sleep_start();
  return 0; 
}
static int doreset(int argc,char *argv[]) { esp_restart(); return 0; }
static int dofault(int argc,char *argv[]) 
{ 
  char *p=NULL; *p = '5'; return 0; 
}
static int go(int argc,char *argv[]) { return 0; }
static int doexit(int argc,char *argv[]) { return 3; }
static int dohelp(int argc,char *argv[]);
static int doi2ccmd(int argc,char *argv[])
{
  if(argc < 4) {
  usage:    
    myprintf("Usage: i2c r|w i2cdevaddr(hex) readcount|write-ahex-data\r\n");
    return 0;
  }
  //myprintf("|%s|%s|%s|\r\n",argv[1],argv[2],argv[3]);
  int i;
  uint8_t i2caddr[8];
  if(strcasecmp(argv[1],"r") == 0 || strcasecmp(argv[1],"w") == 0) {
    if(strlen(argv[2]) > 2 || (i=ahex2bin(i2caddr,argv[2])) != 1)  goto usage;
    //myprintf("i2c: %s 0x%02X %s\r\n",argv[1],i2caddr[0],argv[3]);
    if(strcasecmp(argv[1],"r") == 0) {
      if((i=atoi(argv[3])) <= 0) goto usage;
      uint8_t buf[i];
      mymutexI2C(1);
      i = myi2cread(i2caddr[0],buf,i,0);
      mymutexI2C(0);
      if(i > 0) adump(buf,i);
      else myprintf(" read error %d\r\n",i);      
    } else if(strcasecmp(argv[1],"w") == 0) {
      if((i=strlen(argv[3])) <= 0) goto usage;
      uint8_t buf[(i/2)+2];
      if((i=ahex2bin(buf,argv[3])) <= 0) goto usage;
      mymutexI2C(1);
      i = myi2cwrite(i2caddr[0],buf,i,0);
      mymutexI2C(0);
      if(i <= 0) myprintf(" write error %d\r\n",i);      
    }
  } else {
    goto usage;
  }
  return 0;
}
static int dosercmd(int argc,char *argv[])
{
  if(argc < 3) {
  usage:    
    myprintf("Usage: ser r|w|b readcount|write-ahex-data|baud\r\n");
    return 0;
  }
  if(myuart0_bad) {
    myprintf("Could not setup UART0\n");
    return -1;
  }
  int i;
  if(strcasecmp(argv[1],"b") == 0) {
    if(myuart0_setbaud(atoi(argv[2]))) {
      myprintf("Could not set baud to %s\n",argv[2]);
    }
    return 0;
  }
  if(strcasecmp(argv[1],"r") && strcasecmp(argv[1],"w")) goto usage;
  if(strcasecmp(argv[1],"r") == 0) {
    if((i=atoi(argv[2])) <= 0) goto usage;
    uint8_t buf[i];
    mymutexI2C(1);
    i = myuart0_read(buf,i,1); // 1 tick wait?
    mymutexI2C(0);
    if(i > 0) {
      myprintf(" ");
      adump(buf,i);
    } else myprintf("read error %d\r\n",i);      
  } else if(strcasecmp(argv[1],"w") == 0) {
    if((i=strlen(argv[2])) <= 0) goto usage;
    uint8_t buf[(i/2)+2];
    if((i=ahex2bin(buf,argv[2])) <= 0) goto usage;
    mymutexI2C(1);
    i = myuart0_write(buf,i,0);
    mymutexI2C(0);
    if(i <= 0) myprintf(" write error %d\r\n",i);      
  }
  return 0;
}

struct command {
  char *name;
  int (*function)(int argc,char *argv[]);
};
static struct command cmds[] = {
  "",go, /* first */
  "?",dohelp,
  "exit",doexit,
  "status",mystats,
  "sleep",dosleep,
  "reset",doreset,
  "fault",dofault,
  "i2c",doi2ccmd,
  "ser",dosercmd,
  (char *)0,NULL,
};
static int dohelp(int argc,char *argv[]) {
  struct command *cm;
  for(cm=cmds;cm->name;cm++) if(strlen(cm->name) > 0) myprintf(" %s\r\n",cm->name);
  return 0;
}

#define COMMAND_MAX_ARGS 20
static int cparse(char *line)
{
  struct command *cm;
  char *argv[COMMAND_MAX_ARGS];
  int argc;
  myprintf("%s\r\n",line); // conprintf("%s\n",line);
  argc = lparse(line,argv,COMMAND_MAX_ARGS,' ');
  if(argv[0] == (char *)0) return 0;
  if(argv[0][0] == '#') return 0; // comments
  for(cm=cmds;cm->name;cm++) {
    if(strncmp(argv[0],cm->name,strlen(argv[0])) == 0) break; // partial match
  }
  if(cm->name == (char *)0) {
    myprintf("Unknown Command\r\n");
    return -1; /* No command */
  }
  return (*cm->function)(argc,argv);
}


/******************************************************************************
 * UART/CDC
 *****************************************************************************/
#include "driver/usb_serial_jtag.h"
static int (*cdc0respproc)(char *in,int state);
static uint8_t cdc0cont=0;

#ifdef ESP32S2
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"

static void cdc1_callback(int itf,cdcacm_event_t *ev)
{
  if(ev->type != CDC_EVENT_RX) {
    if(ev->type != CDC_EVENT_LINE_STATE_CHANGED) return;
    int dtr = ev->line_state_changed_data.dtr;
    int rts = ev->line_state_changed_data.rts;
    myprintf("Changed DTR:%d, RTS:%d\r\n",dtr,rts);
    uart_set_rts(UART_NUM_0,rts);
    uart_set_dtr(UART_NUM_0,dtr);
    return;
  }
  size_t rxsz;
  uint8_t buf[128];
  while(1) {
    rxsz = 0;
    tinyusb_cdcacm_read(TINYUSB_CDC_ACM_1,buf,sizeof(buf),&rxsz);
    if(rxsz <= 0) break;
    uart_write_bytes(UART_NUM_0,(const char*)buf,rxsz);
    uart_wait_tx_done(UART_NUM_0,10); // 10 ticks - adj for Baud? or block
  }
}
static void cdc1_proc(void)
{
  size_t n;
  uint8_t buf[64];
  while(1) {
    uart_get_buffered_data_len(UART_NUM_0,&n);
    if(n <= 0) break;
    n = uart_read_bytes(UART_NUM_0,buf,min(n,sizeof(buf)),0);
    tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_1,buf,n);
    tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_1,0); // blocks if timeout != 0?
  }
}

typedef const char *tusb_desc_strarray_device_t[5];
static char cdc0serial[80];
static tusb_desc_strarray_device_t cdc0strings = {
  (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
  "Rick Lamb",  // 1: Manufacturer
  "i2c2ser", // 2: Product
  cdc0serial, // 3: Serials, should use chip ID
  NULL
};
#endif // ESP32S2
static void cdc0_init(void)
{
#ifdef ESP32S2
  sprintf(cdc0serial,"%02x%02x%02x%02x%02x%02x",
	  mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
  const tinyusb_config_t tusb_cfg = {
    .device_descriptor = NULL,
    //.string_descriptor = NULL,
    .string_descriptor = cdc0strings,
    .external_phy = false,
    .configuration_descriptor = NULL,
  };
  ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));  
  tinyusb_config_cdcacm_t acm_cfg = {
    .usb_dev = TINYUSB_USBDEV_0,
    .cdc_port = TINYUSB_CDC_ACM_0,
    .rx_unread_buf_sz = 256,
    .callback_rx = NULL,
    .callback_rx_wanted_char = NULL,
    .callback_line_state_changed = NULL,
    .callback_line_coding_changed = NULL
  };
  ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));

  esp_tusb_init_console(TINYUSB_CDC_ACM_0); // causes some rx noise for this app

  // second CDC if available
  acm_cfg.cdc_port = TINYUSB_CDC_ACM_1;
  acm_cfg.callback_rx = cdc1_callback;
  acm_cfg.callback_line_state_changed = cdc1_callback;
  ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
#endif // ESP32S2
#ifdef ESP32C3  
  usb_serial_jtag_driver_config_t usb_serial_config = {
    .tx_buffer_size = 256,
    .rx_buffer_size = 256
  };
  usb_serial_jtag_driver_uninstall(); // seem to need this after soft reset
  ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb_serial_config));
  printf("%s: installed\r\n",__func__); // NOTE: For CDC driver, raw console printf needed after sw reset.
#endif // ESP32C3
}

static void cdc0_proc(int flush)
{
  static int i=0;
  static char lbuf[1024];
  int c;
  char cbuf[2];

  if(flush) i = 0;
  
  while(1) {
#ifdef ESP32S2
    //if(mycdc_itf < 0) break;
    c = 0;
    tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0,(uint8_t *)cbuf,1,(size_t *)&c);
    if(c <= 0) break;
#endif // ESP32S2
#ifdef ESP32C3    
    if(usb_serial_jtag_read_bytes(cbuf,1,0/portTICK_PERIOD_MS) <= 0) break;
#endif // ESP32C3    
    c = (int)cbuf[0];    
    if(c == '\r' || c == '\n') {      
      if(cdc0cont) {
	lbuf[i] = '\0';
	if(i == 0 || (i > 0 && lbuf[0] != ' ')) {
	  cdc0cont = 0;
	  if(cdc0respproc) {
	    cdc0respproc(lbuf,2); // end
	    cdc0respproc = NULL;
	  }
	} else if(i > 1) {
	  if(cdc0respproc) cdc0respproc(&lbuf[1],1); // middle
        }
      } else { // console
	lbuf[i] = '\0';      
	cparse(lbuf);
      }      
      i = 0;
    } else {
      if(i < (int)(sizeof(lbuf)-1)) lbuf[i++] = (char)c;
    }
  }
}
static void cdc0_write(uint8_t *p,int n)
{
#ifdef ESP32S2
  int i;
  while(n > 0) {
    i = min(n,64);
    tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0,p,i);
    tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0,0); // blocks if timeout != 0? 
    p += i;
    n -= i;
  }
#endif // ESP32S2  
#ifdef ESP32C3
  //if(usb_serial_jtag_is_connected())
  usb_serial_jtag_write_bytes((const void*)p,(size_t)n,0);
#endif // ESP32C3
}
  
static int myloop(void)
{
  static uint8_t init=1;
  
  if(init) { // approx 400ms from wake trigger to here
    init = 0;
    mystarttime = mytime(NULL);
    // watchdog setup  WATCHDOGTIMEOUT*1000
    esp_task_wdt_config_t config = {
      .timeout_ms = 10*1000,
      .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
      .trigger_panic = false
    };
    if(esp_task_wdt_status(NULL) == ESP_ERR_INVALID_STATE) {
      ESP_ERROR_CHECK(esp_task_wdt_init(&config));
    } else {
      ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&config));
    }
    ESP_ERROR_CHECK(esp_task_wdt_add(NULL));

    esp_efuse_mac_get_default(mymac); //  for /dev/serial/by-id/..
    cdc0_init();
    myi2cinit();
    myuart0_init();

    myprintf("ONCE init mac:%02X:%02X:%02X:%02X:%02X:%02X\r\n",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
  }

  esp_task_wdt_reset();  vTaskDelay(1); // need this to let idle task cleanup

  // do main loop stuff here
  cdc0_proc(0);
  cdc1_proc();
  
  static int tcnt=0;
  if(tcnt++ > 300) {
    tcnt = 0;
    // misc other supervisory stuff
  }
  return 0;
}

void app_main(void)
{
  while(1) {
    myloop();
  }
  esp_restart();
}

sampleserio.c
/*                                                                              
 * Copyright Department C Inc. 2023
 *
 * Host example for ESP32C3 "Serial I2C Convertor"
 * R H Lamb 2023
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL DEPARTMENT C INC BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * cc this.c -o this
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define min(x, y) ( ((x)<(y))?(x):(y) )

/***********************************************************
 * /dev/ttyACM0 serial I/O
 ***********************************************************/
static int serial_fd=-1;
static  struct termios serial_oldtio,serial_newtio;
// static int epollfd;  // other way to do this. removed for clarity
static int serial_init(char *dev)
{
  if(serial_fd >= 0) return 0;  

  if((serial_fd=open(dev,(O_RDWR|O_NOCTTY))) < 0) {
    printf("Cant open %s\n",dev);
    return -1;
  }
  if(tcgetattr(serial_fd,&serial_oldtio)) {
    printf(" tcgetattr failed\n");
    close(serial_fd);
    serial_fd = -1;
    return -1;
  }
  serial_newtio = serial_oldtio;
  serial_newtio.c_iflag &= ~(ICRNL); // !CR->NL
  serial_newtio.c_lflag &= ~(ICANON|ECHO);
  serial_newtio.c_cc[VTIME] = 200; /* 10 = 1 sec timeout */
  serial_newtio.c_cc[VMIN] = 0; /* return from read on any char rcvd */  
  if(tcsetattr(serial_fd,TCSANOW,&serial_newtio)) {
    printf(" tcsetattr failed\n");
    close(serial_fd);
    serial_fd = -1;    
    return -1;
  }
  printf("Openned %s\n",dev);
  fflush(stdout);
  return 0;  
}
static void serial_rts(int onoff) // RTS=1 at pwr up
{
  int flag;
  flag = TIOCM_RTS;
  if(onoff == 0) ioctl(serial_fd,TIOCMBIS,&flag); // RTS=0
  else ioctl(serial_fd,TIOCMBIC,&flag); // RTS=1
}  
static void serial_dtr(int onoff) // DTR=1 at pwr up
{
  int flag;
  flag = TIOCM_DTR;
  if(onoff == 0) ioctl(serial_fd,TIOCMBIS,&flag); // DTR=0
  else ioctl(serial_fd,TIOCMBIC,&flag); // DTR=1
}

/***********************************************************
 * Extra serial port buffering. Left out epoll event version
 ***********************************************************/
#include 
static uint8_t rxlistenerrun=0,rxbuffer[1024];
static int rxhead=0,rxtail=0;
static pthread_t ths;
static int seriallistener(void)
{
  int n;
  uint8_t ib[2];
  while(rxlistenerrun) {
    n = read(serial_fd,ib,1);

    if(n == 0) continue;
    
    if(n < 0) break;
    if(n > 1) break;
    rxbuffer[rxtail] = ib[0];
    rxtail = (rxtail + 1)%sizeof(rxbuffer);
    if(rxtail == rxhead) rxhead = (rxhead + 1)%sizeof(rxbuffer);
  }
  printf("%s: Exited!!\n",__func__);
  return 0;
}
static int serial_read(uint8_t *ib,int wantlen,int timeout)
{
  uint8_t *p=ib;
  int n=0;
  int tmo=(timeout*1000);
  if(rxlistenerrun == 0) {
    pthread_create(&ths,NULL,(void *)seriallistener,NULL);
    rxlistenerrun = 1;
  }
  while(tmo > 0) {
    while(rxtail != rxhead && wantlen > 0) {
      *p++ = rxbuffer[rxhead];
      wantlen--;
      n++;
      rxhead = (rxhead + 1)%sizeof(rxbuffer);
      if(rxtail == rxhead) break;
    }
    if(wantlen == 0) return n;
    usleep(1000);
    tmo -= 1;
  }
  return n;
}
static int serial_getline(char *ib,int maxlen,int timeout)
{
  uint8_t *p=ib;
  int n=0;
  int tmo=(timeout*1000);
  if(rxlistenerrun == 0) {
    pthread_create(&ths,NULL,(void *)seriallistener,NULL);
    rxlistenerrun = 1;
  }
  while(tmo > 0) {
    while(rxtail != rxhead && maxlen > 0) {
      uint8_t c=rxbuffer[rxhead];
      rxhead = (rxhead + 1)%sizeof(rxbuffer);      
      if(c == (uint8_t)'\r') {
	*p++ = '\0';
	return n;
      }
      if(c == (uint8_t)'\n') continue;
      *p++ = c;
      maxlen--;
      n++;
    }
    if(maxlen == 0) return n;
    usleep(1000);
    tmo -= 1;
  }
  return n;
}

/***********************************************************
 * Misc support funcs
 ***********************************************************/
static int rdump(uint8_t *ptr,int n)
{
  int i,j1,j2; char buf[80]; static char htoas[]="0123456789ABCDEF";
  j1 = j2 = 0; /* gcc -W */
  for(i=0;i> 4]; buf[j1+1] = htoas[ptr[i]&0x0f];
    if(ptr[i] >= 0x20 && ptr[i] < 0x80) buf[j2]=ptr[i]; else buf[j2]='.';
  }
  buf[j2]='\0'; printf("%s|\n",buf);
  return 0;
}
static int bin2ahex(char *out,uint8_t *in,int n)
{
  int i;
  char *p=out;
  static char htoas[]="0123456789ABCDEF";
  for(i=0;i> 4];
    *p++ = htoas[in[i]&0x0f];
  }
  *p++ = '\0';
  return (int)(p - out);
}
static int hex2i(char c)
{
  if(c >= '0' && c <= '9') return (int)(c - '0');
  if(c >= 'A' && c <= 'F') return (int)((c - 'A') + 10);
  if(c >= 'a' && c <= 'f') return (int)((c - 'a') + 10);
  return -1;
}
static int ahex2bin(uint8_t *out,char *in)
{
  uint8_t *p;
  int i,j;
  p = out;
  while(*in) {
    i = hex2i(in[0]);
    if(in[1]) j = hex2i(in[1]); else j = 0;
    if(i < 0 || j < 0) return -1;
    *p++ = (uint8_t) ((i<<4)|j);
    if(in[1]) in += 2; else in++;
  }
  return (int)(p - out);
}

/***********************************************************
 * These simulate the real I2C function in the ESP32C3/S2
 * All I/O in plain ASCII
 ***********************************************************/
static int wdt=0; // connection watchdog
static int myi2cwrite(uint8_t addr,uint8_t *p,int n,int timeout)
{
  char *q,*r;
  int i;
  q = (char *)malloc((2*n)+4);
  r = (char *)malloc((2*n)+32);  
  bin2ahex(q,p,n);
  i = sprintf(r,"i2c w %02x %s\r",addr,q);
  free(q);
  write(serial_fd,r,i);
  free(r);

  usleep(10000);
  
  return n;
}
static int myi2cread(uint8_t addr,uint8_t *p,int n,int timeout)
{
  char cbuf[512];
  int i;
  i = sprintf(cbuf,"i2c r %02x %d\r",addr,n);
  write(serial_fd,cbuf,i);
  while(serial_getline(cbuf,sizeof(cbuf),timeout) > 0) {
    //printf("%s: |%s|\n",__func__,cbuf);
    wdt = 0; // any resp indicates connected
    i = ahex2bin(cbuf,cbuf);
    if(i == n) {
      memcpy(p,cbuf,n);
      //rdump(p,n);
      return n;
    }
  }
  return 0;
}
/***********************************************************
 * These send/recv data in/out of ESP32C3/S2 UART0
 * All I/O in plain ASCII
 ***********************************************************/
static int myserbaud(int baud)
{
  char cbuf[64];
  int i;
  i = sprintf(cbuf,"ser b %d\r",baud);
  write(serial_fd,cbuf,i);
  return 0;
}
static int myserwrite(uint8_t *p,int n,int timeout)
{
  char *q,*r;
  int i;
  q = (char *)malloc((2*n)+4);
  r = (char *)malloc((2*n)+32);
  bin2ahex(q,p,n);
  i = sprintf(r,"ser w %s\r",q);
  free(q);
  write(serial_fd,r,i);
  free(r);

  usleep(10000);

  return n;
}
static int myserread(uint8_t *p,int n,int timeout)
{
  char cbuf[512];
  int i;
  i = sprintf(cbuf,"ser r %d\r",n);
  write(serial_fd,cbuf,i);
  while(serial_getline(cbuf,sizeof(cbuf),timeout) > 0) {
    //printf("%s: |%s|\n",__func__,cbuf);
    wdt = 0; // any resp indicates connected
    if(cbuf[0] == ' ') {
      i = ahex2bin(&cbuf[1],&cbuf[1]);    
      memcpy(p,&cbuf[1],i);
      //rdump(p,n);
      return i;
    }
  }
  return 0;
}

/**************************************************************
 * DS3231
 **************************************************************/
static const int mymt[]={0,31,59,90,120,151,181,212,243,273,304,334};
static uint32_t date2secs(int y,int m,int d,int h,int M,int s)
{
  uint32_t t;
  // date to seconds since epoch
  t = (y-1)/4 - ((1970-1)/4) - (y-1)/100 + ((1970-1)/100) + (y-1)/400 - ((1970-1)/400);
  t += 365*(y-1970);
  t += mymt[m];
  if(m > 0 && ((y%4 == 0)&&((y%100 != 0)||(y%400 == 0)))) t++;
  t += d;
  t = s + (60*M) + (3600*h) + (86400*t);
  return t;
}

static void displine(int x,int y,char *s);

static int ds3231proc(char *utc)
{
  uint8_t buf[32];
  int n,y,m,d,H,M,S;
  if(utc) {
    buf[0] = 0x00;
    // 20130621235959
    buf[1] = ((utc[12]-0x30)<<4) | (utc[13]-0x30); // sec
    buf[2] = ((utc[10]-0x30)<<4) | (utc[11]-0x30); // min
    buf[3] = ((utc[8]-0x30)<<4) | (utc[9]-0x30); // hr
    buf[4]  = 0; // dow
    buf[5] = ((utc[6]-0x30)<<4) | (utc[7]-0x30); // day
    buf[6] = ((utc[4]-0x30)<<4) | (utc[5]-0x30) | ((utc[1]=='1')?0x80:0); // mo
    buf[7] = ((utc[2]-0x30)<<4) | (utc[3]-0x30); // yr - 2000
    //rdump(buf,8);
    myi2cwrite(0x68,buf,8,1);
  }
  // force temperature update
  //buf[0] = 0x0e;
  //buf[1] = 0x3C;
  //myi2cwrite(0x68,buf,2,1);  
  buf[0] = 0x00;
  myi2cwrite(0x68,buf,1,1);
  n = myi2cread(0x68,buf,0x12+1,1);
  // 59 01 00 01 01 01 00 00 00 00 00 00 00 00 1C 88
  //rdump(buf,n);
  S = (buf[0]&0xF) + ((buf[0]>>4)*10);
  M = (buf[1]&0xF) + ((buf[1]>>4)*10);
  H = (buf[2]&0xF) + ((buf[2]&0x10)?10:0) + ((buf[2]&0x20)?20:0);
  d = (buf[4]&0xF) + ((buf[4]>>4)*10);
  m = (buf[5]&0xF) + (((buf[5]&0x10)>>4)*10);
  y = (buf[6]&0xF) + ((buf[6]>>4)*10) + ((buf[5]&0x80)?100:0) + 2000;

  double tp;
  tp = (double)((int8_t)buf[0x11]);
  if(tp < 0) {
    tp = -tp;
    tp += ((double)(buf[0x12]>>6))/4.0;
    tp = -tp;
  } else {
    tp += ((double)(buf[0x12]>>6))/4.0;
  }

  // compare with NTP
  int x;
  time_t curtime;
  time(&curtime);
  x = (int)(curtime - (time_t)date2secs(y,m-1,d-1,H,M,S));
  
  printf("%04u%02u%02u%02u%02u%02u temp=%.2f ntp-ds3231=%dsec\n",
	 y,m,d,H,M,S,tp,x);
  char lbuf[256];
  sprintf(lbuf,"%04u%02u%02u%02u%02u%02u temp=%.2f ntp-ds3231=%dsec    ",
	  y,m,d,H,M,S,tp,x);
  displine(1,1,lbuf);

  return 0;
}

/**************************************************************
 * Some example I2C device I/O 
 * Using a Digole LCD w/ Touch interface
 * https://www.digole.com/images/file/Tech_Data/Digole_Serial_Display_Adapter-Manual%20V4.pdf
 * NOTICE that you can literally copy this code into the 
 * accompanying ESP32C3 source to perform these same functions
 * directly on I2C h/w.  The myi2cread/write calls are the same.
 **************************************************************/
static int digoleinit(void)
{
#define DISP_INIT1 "BGC\x00"
#define DISP_INIT1a "SC\xFF"  // 0F
#define DISP_INIT1b "DMC"
#define DISP_INIT2 "CL"
#define DISP_INIT3 "SD1"
#define DISP_INIT4 "TT\r\nHello there\x00"
#define DISP_INIT5 "BL100"
#define DISP_INIT6 "SF120" // SF18

  myi2cwrite(0x27,(uint8_t *)DISP_INIT6,sizeof(DISP_INIT6),0);

  myi2cwrite(0x27,(uint8_t *)DISP_INIT1,sizeof(DISP_INIT1),0);
  myi2cwrite(0x27,(uint8_t *)DISP_INIT1a,sizeof(DISP_INIT1a),0);
  myi2cwrite(0x27,(uint8_t *)DISP_INIT1b,sizeof(DISP_INIT1b),0);
  myi2cwrite(0x27,(uint8_t *)DISP_INIT2,sizeof(DISP_INIT2),0);
  myi2cwrite(0x27,(uint8_t *)DISP_INIT3,sizeof(DISP_INIT3),0);

  myi2cwrite(0x27,(uint8_t *)DISP_INIT5,sizeof(DISP_INIT5),0);
  myi2cwrite(0x27,(uint8_t *)DISP_INIT6,sizeof(DISP_INIT6),0);  
  return 0;
}
static void displine(int x,int y,char *s)
{
  char cbuf[256];
  int n;

  // Using custom code in ESP32C3 for speed
  //n = sprintf(cbuf,"displine %d %d \"%s\"\r",x,y,s); // y=1-17 x=1-51
  //write(serial_fd,cbuf,n);

  // Using generic i2c i/o
  cbuf[0] = 'T';
  cbuf[1] = 'P';
  cbuf[2] = (uint8_t)x;
  cbuf[3] = (uint8_t)y;
  myi2cwrite(0x27,cbuf,4,0);  
  n = sprintf(cbuf,"TT%s",s);
  myi2cwrite(0x27,cbuf,n+1,0); // sprintf appends needed 0x00
}
#define KEYPADX0 100 // upper left corner of keypad
#define KEYPADY0 47
#define KEYPADSPACING 34
#define KEYPADBRAD ((KEYPADSPACING/2) - 2)
static int dispwnum(uint8_t *p,int num)
{
  if(num >= 255) {
    *p++ = 0xFF;
    *p = (uint8_t)(num - 255);
    return 2;
  }
  *p = (uint8_t)num;
  return 1;
}
static int disptouch(void)
{
  static uint8_t init=0;
  int x,y,i;
  uint8_t buf[16];
  if(init == 0) {
    int j,k;
    for(j=0;j<4;j++) {
      for(k=0;k<3;k++) {
	x = KEYPADX0 + (KEYPADSPACING/2) + (k*KEYPADSPACING);
	y = KEYPADY0 + (KEYPADSPACING/2) + (j*KEYPADSPACING);
	// CCxyrf
	i = 0;
	buf[i++] = 'C';
	buf[i++] = 'C';
	i += dispwnum(&buf[i],x);
	i += dispwnum(&buf[i],y);
	buf[i++] = KEYPADBRAD;
	buf[i++] = 1; // fill
	myi2cwrite(0x27,buf,i,0);
	// ETPxy
	x -= 3;
	y += 3;
        i = 0;
        buf[i++] = 'E';
        buf[i++] = 'T';
	buf[i++] = 'P';
	i += dispwnum(&buf[i],x);
	i += dispwnum(&buf[i],y);
        myi2cwrite(0x27,buf,i,0);
	// TT..\x00
	buf[0] = 'T';
	buf[1] = 'T';
	i = (3*j) + k + 1;
	switch(i) {
	case 10: buf[2] = '*'; break;
	case 11: buf[2] = '0'; break;
	case 12: buf[2] = '#'; break;
	default: buf[2] = (uint8_t)(0x30 + i); break;
	}
	buf[3] = 0x00;
	myi2cwrite(0x27,buf,4,0);
      }
    }
    init = 1;
  }
  myi2cwrite(0x27,(uint8_t *)"RPNXYI",6,0);
  memset(buf,0,4);
  if(myi2cread(0x27,buf,4,1) == 4 && buf[0] != 0xFF) {
    x = (int)htons(*(uint16_t *)&buf[0]);
    y = (int)htons(*(uint16_t *)&buf[2]);
    if(x > 400 || y > 240) return 0; // catch errors beyond disp size
    //printf("event: XY=%d %d\r\n",x,y);
    x -= KEYPADX0;
    y -= KEYPADY0;
    if(x < 0 || y < 0 || x > (3*KEYPADSPACING) || y > (4*KEYPADSPACING)) return -1;
    if(x < KEYPADSPACING) { // 1 4 7 *
      if(y < KEYPADSPACING) { return 1;
      } else if(y < (2*KEYPADSPACING)) { return 4;
      } else if(y < (3*KEYPADSPACING)) { return 7;
      } else { return 10; }
    } else if(x < (2*KEYPADSPACING)) { // 2 5 8 0
      if(y < KEYPADSPACING) { return 2;
      } else if(y < (2*KEYPADSPACING)) { return 5;
      } else if(y < (3*KEYPADSPACING)) { return 8;
      } else { return 11; }
    } else { // 3 6 9 #
      if(y < KEYPADSPACING) { return 3;
      } else if(y < (2*KEYPADSPACING)) { return 6;
      } else if(y < (3*KEYPADSPACING)) { return 9;
      } else { return 12; }
    }
    printf("%s: error\r\n",__func__);
  }  
  return -1;
}

int main(int argc,char *argv[])
{
  char *device;
  char buf[128];
  int i,toggle,tcnt=0;
  time_t curtime;

  /*
   * Using /dev/serial/by-id will make sure you get the right ttyACM0 
   * even after server reboot and reconfiguration.
   * ESP32S2 has unique mac address embedded in name "_i2c2ser_4827e259f140.."
   * as does ESP32C3 serial_debug_unit_64:E8:33:13:CB:7C...
   */
  device = "/dev/ttyACM0";
  //device = "/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_64:E8:33:13:CB:7C-if00";
  //device = "/dev/serial/by-id/usb-Rick_Lamb_i2c2ser_4827e259f140-if00";
  if(argc > 1) device = strdup(argv[1]);  
  if(serial_init(device) < 0) {
    printf("Cant open device %s\n",device);
    return -1;
  }
  if(argc > 2) {
    if(strcmp(argv[2],"r") == 0) serial_rts(0);
    else if(strcmp(argv[2],"R") == 0) serial_rts(1);
    else if(strcmp(argv[2],"d") == 0) serial_dtr(0); 
    else if(strcmp(argv[2],"D") == 0) serial_dtr(1);
    else {
      printf("Unknown command\n");
    }
    return 0;
  }

  
  digoleinit();
  //time(&curtime);
  //strftime(buf,sizeof(buf),"%Y%m%d%H%M%S",gmtime(&curtime));
  //ds3231proc(buf);
  wdt = 0;
  while(1) {
    usleep(100000);
    if((i=disptouch()) >= 0) {
      sprintf(buf,"Pressed %2d",i);
      displine(1,5,buf);
      printf(" %d\r\n",i);
    }
    // causes response to clear wdt and activity on lcd
    if(toggle++&0x1) displine(1,16,"*"); else displine(1,16," ");
    if(wdt++ > 10) {
      printf("System watchdog failed\n");
    }
    if(tcnt++ > 11) {
      tcnt = 0;
      ds3231proc(NULL);
    }
    
    i = sprintf(buf,"Hello %d",tcnt);
    myserwrite(buf,i,1);
    if((i=myserread(buf,sizeof(buf)-1,1)) > 0) {
      buf[i] = 0;
      printf("ser=|%s|\n",buf);
    }

    fflush(stdout);    
  }
 endit:
  close(serial_fd);    
  return 0;
}