iy3xk ftc9ky
$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 ESP32S2 module (right picture) you can have the same functionality. Yay!

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
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
#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;
}
/******************************************************************************
* Stats
******************************************************************************/
#include "esp_psram.h"
static int mystats(int argc,char *argv[])
{
char lbuf[128];
{
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());
char *foo;
if((foo=heap_caps_malloc(1000000,MALLOC_CAP_SPIRAM))) {
myprintf("allocated 1M\r\n");
heap_caps_free(foo);
} else {
myprintf("Could not allocat psram\r\n");
}
#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;
}
//myprintf("%s: done\r\n",__func__);
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,
(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/uart.h"
#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 int mycdc_itf=-1;
static int mytxrdy=0;
void tud_cdc_tx_complete_cb(uint8_t itf)
{
mytxrdy = 1;
}
void tinyusb_cdc_line_state_changed_callback(int itf,cdcacm_event_t *event)
{
int dtr = event->line_state_changed_data.dtr;
int rts = event->line_state_changed_data.rts;
if(mycdc_itf < 0) mycdc_itf = itf;
//myprintf("Line state changed on channel %d: DTR:%d, RTS:%d\r\n",itf,dtr,rts);
}
#endif // ESP32S2
static void cdc0_init(void)
{
#ifdef ESP32S2
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.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 = 64,
.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_ERROR_CHECK(tinyusb_cdcacm_register_callback(
TINYUSB_CDC_ACM_0,
CDC_EVENT_LINE_STATE_CHANGED,
&tinyusb_cdc_line_state_changed_callback));
esp_tusb_init_console(0);
#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(void)
{
static int i=0;
static char lbuf[1024];
int c;
char cbuf[2];
while(1) {
#ifdef ESP32S2
if(mycdc_itf < 0) break;
c = 0;
tinyusb_cdcacm_read(mycdc_itf,(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(mycdc_itf,p,i);
tinyusb_cdcacm_write_flush(mycdc_itf,1);
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));
cdc0_init();
myi2cinit();
esp_efuse_mac_get_default(mymac); // /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_[mymac]-if00 -> ../../ttyACM0
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();
static int tcnt=0;
if(tcnt++ > 100) {
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/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.
*
* cc this.c -o this
*/
//#define SMDISP // define if 320x240 small display. default:400x240
#define DOTOUCH // define if display supports touch. default:enable
#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;
}
/***********************************************************
* 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
* 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;
}
/**************************************************************
* 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];
#ifdef SMDISP
sprintf(lbuf,"%02u:%02u:%02u t=%.2f e=%dsec ",H,M,S,tp,x);
#else // SMDISP
sprintf(lbuf,"%04u%02u%02u%02u%02u%02u temp=%.2f ntp-ds3231=%dsec ",
y,m,d,H,M,S,tp,x);
#endif // SMDISP
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 "SF\x12" // 9x18bold
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;
}
#ifdef DOTOUCH
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__);
}
#else // DOTOUCH
wdt = 0;
#endif // DOTOUCH
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
//device = "/dev/ttyACM0";
device = "/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_64:E8:33:13:CB:7C-if00";
if(argc > 1) device = strdup(argv[1]);
if(serial_init(device) < 0) {
printf("Cant open device %s\n",device);
return -1;
}
digoleinit();
#ifdef INITTIME
time(&curtime);
strftime(buf,sizeof(buf),"%Y%m%d%H%M%S",gmtime(&curtime));
ds3231proc(buf);
#endif // INITTIME
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);
}
fflush(stdout);
}
endit:
close(serial_fd);
return 0;
}