DEV Community

jay jordan
jay jordan

Posted on

portable television with hologram function

/***

// Audio and video code

// ffmpeg -i office1.mp4 -ar 44100 -ac 1 -ab 24k -filter:a loudnorm -filter:a "volume=-5dB" office1.aac
// ffmpeg -i office1.mp4 -vf "fps=25,scale=-1:240:flags=lanczos,crop=288:in_h:(in_w-288)/2:0" -q:v 11 office1.mjpeg
// auto fall back to MP3 if AAC file not available

define AAC_FILENAME "/.aac"

define MP3_FILENAME "/.mp3"

// #define MP3_FILENAME "/EP05.mp3"

define MJPEG_FILENAME "/.mjpeg"

// #define MJPEG_FILENAME "/EP05.mjpeg"
// #define MJPEG_FILENAME "/320_30fps.mjpeg"

define FPS 25

define MJPEG_BUFFER_SIZE (288 * 240 * 2 / 8)

// #define MJPEG_BUFFER_SIZE (320 * 240 * 2 / 8)

define AUDIOASSIGNCORE 1

define DECODEASSIGNCORE 0

define DRAWASSIGNCORE 1

include

include

include

include

include

include

include

/* Arduino_GFX */

include

define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin

Arduino_DataBus bus = create_default_Arduino_DataBus();
// Arduino_GFX *gfx = new Arduino_ILI9341(bus, DF_GFX_RST, 3 /
rotation /, false / IPS /);
Arduino_GFX *gfx = new Arduino_ST7789(bus, DF_GFX_RST, 1 /
rotation /, true / IPS /, 240 / width /, 288 / height /, 0 / col offset 1 /, 20 / row offset 1 /, 0 / col offset 2 /, 12 / row offset 2 */);

/* variables */
static int next_frame = 0;
static int skipped_frames = 0;
static unsigned long start_ms, curr_ms, next_frame_ms;

/* audio */

include "esp32_audio_task.h"

/* MJPEG Video */

include "mjpeg_decode_draw_task.h"

// pixel drawing callback
static int drawMCU(JPEGDRAW pDraw)
{
// Serial.printf("Draw pos = (%d, %d), size = %d x %d\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
unsigned long s = millis();
gfx->draw16bitRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight);
total_show_video_ms += millis() - s;
return 1;
} /
drawMCU() */

void setup()
{
disableCore0WDT();

WiFi.mode(WIFI_OFF);
Serial.begin(115200);
// while (!Serial);

// Init Display
gfx->begin(80000000);
gfx->fillScreen(BLACK);

ifdef GFX_BL

pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);

endif

Serial.println("Init I2S");
gfx->println("Init I2S");

if defined(ESP32) && (CONFIG_IDF_TARGET_ESP32)

esp_err_t ret_val = i2s_init(I2S_NUM_0, 44100, -1 /* MCLK /, 25 / SCLK /, 26 / LRCK /, 32 / DOUT /, -1 / DIN */);

elif defined(ESP32) && (CONFIG_IDF_TARGET_ESP32S2)

esp_err_t ret_val = i2s_init(I2S_NUM_0, 44100, -1 /* MCLK /, 4 / SCLK /, 5 / LRCK /, 18 / DOUT /, -1 / DIN */);

elif defined(ESP32) && (CONFIG_IDF_TARGET_ESP32S3)

esp_err_t ret_val = i2s_init(I2S_NUM_0, 44100, 42 /* MCLK /, 46 / SCLK /, 45 / LRCK /, 43 / DOUT /, 44 / DIN */);

elif defined(ESP32) && (CONFIG_IDF_TARGET_ESP32C3)

esp_err_t ret_val = i2s_init(I2S_NUM_0, 44100, -1 /* MCLK /, 10 / SCLK /, 19 / LRCK /, 18 / DOUT /, -1 / DIN */);

endif

if (ret_val != ESP_OK)
{
Serial.printf("i2s_init failed: %d\n", ret_val);
}
i2s_zero_dma_buffer(I2S_NUM_0);

Serial.println("Init FS");
gfx->println("Init FS");
// if (!LittleFS.begin(false, "/root"))
// if (!SPIFFS.begin(false, "/root"))
// if (!FFat.begin(false, "/root"))
SPIClass spi = SPIClass(HSPI);
// spi.begin(14 /* SCK /, 2 / MISO /, 15 / MOSI /, 13 / CS /);
spi.begin(14 /
SCK /, 4 / MISO /, 15 / MOSI /, 13 / CS /);
if (!SD.begin(13, spi, 80000000))
// if ((!SD_MMC.begin("/root")) && (!SD_MMC.begin("/root")) && (!SD_MMC.begin("/root")) && (!SD_MMC.begin("/root"))) /
4-bit SD bus mode /
// if ((!SD_MMC.begin("/root", true)) && (!SD_MMC.begin("/root", true)) && (!SD_MMC.begin("/root", true)) && (!SD_MMC.begin("/root", true))) /
1-bit SD bus mode */
{
Serial.println("ERROR: File system mount failed!");
gfx->println("ERROR: File system mount failed!");
}
else
{
bool aac_file_available = false;
Serial.println("Open AAC file: " AAC_FILENAME);
gfx->println("Open AAC file: " AAC_FILENAME);
// File aFile = LittleFS.open(AAC_FILENAME);
// File aFile = SPIFFS.open(AAC_FILENAME);
// File aFile = FFat.open(AAC_FILENAME);
File aFile = SD.open(AAC_FILENAME);
// File aFile = SD_MMC.open(AAC_FILENAME);
if (aFile)
{
aac_file_available = true;
}
else
{
Serial.println("Open MP3 file: " MP3_FILENAME);
gfx->println("Open MP3 file: " MP3_FILENAME);
// aFile = LittleFS.open(MP3_FILENAME);
// aFile = SPIFFS.open(MP3_FILENAME);
// aFile = FFat.open(MP3_FILENAME);
aFile = SD.open(MP3_FILENAME);
// aFile = SD_MMC.open(MP3_FILENAME);
}

if (!aFile || aFile.isDirectory())
{
  Serial.println("ERROR: Failed to open " AAC_FILENAME " or " MP3_FILENAME " file for reading");
  gfx->println("ERROR: Failed to open " AAC_FILENAME " or " MP3_FILENAME " file for reading");
}
else
{
  Serial.println("Open MJPEG file: " MJPEG_FILENAME);
  gfx->println("Open MJPEG file: " MJPEG_FILENAME);
  // File vFile = LittleFS.open(MJPEG_FILENAME);
  // File vFile = SPIFFS.open(MJPEG_FILENAME);
  // File vFile = FFat.open(MJPEG_FILENAME);
  File vFile = SD.open(MJPEG_FILENAME);
  // File vFile = SD_MMC.open(MJPEG_FILENAME);
  if (!vFile || vFile.isDirectory())
  {
    Serial.println("ERROR: Failed to open " MJPEG_FILENAME " file for reading");
    gfx->println("ERROR: Failed to open " MJPEG_FILENAME " file for reading");
  }
  else
  {
    Serial.println("Init video");
    gfx->println("Init video");
    mjpeg_setup(&vFile, MJPEG_BUFFER_SIZE, drawMCU,
                false /* useBigEndian */, DECODEASSIGNCORE, DRAWASSIGNCORE);

    Serial.println("Start play audio task");
    gfx->println("Start play audio task");
    BaseType_t ret_val;
    if (aac_file_available)
    {
      ret_val = aac_player_task_start(&aFile, AUDIOASSIGNCORE);
    }
    else
    {
      ret_val = mp3_player_task_start(&aFile, AUDIOASSIGNCORE);
    }
    if (ret_val != pdPASS)
    {
      Serial.printf("Audio player task start failed: %d\n", ret_val);
      gfx->printf("Audio player task start failed: %d\n", ret_val);
    }

    Serial.println("Start play video");
    gfx->println("Start play video");
    start_ms = millis();
    curr_ms = millis();
    next_frame_ms = start_ms + (++next_frame * 1000 / FPS / 2);
    while (vFile.available() && mjpeg_read_frame()) // Read video
    {
      total_read_video_ms += millis() - curr_ms;
      curr_ms = millis();

      if (millis() < next_frame_ms) // check show frame or skip frame
      {
        // Play video
        mjpeg_draw_frame();
        total_decode_video_ms += millis() - curr_ms;
        curr_ms = millis();
      }
      else
      {
        ++skipped_frames;
        Serial.println("Skip frame");
      }

      while (millis() < next_frame_ms)
      {
        vTaskDelay(pdMS_TO_TICKS(1));
      }

      curr_ms = millis();
      next_frame_ms = start_ms + (++next_frame * 1000 / FPS);
    }
    int time_used = millis() - start_ms;
    int total_frames = next_frame - 1;
    Serial.println("AV end");
    vFile.close();
    aFile.close();

    int played_frames = total_frames - skipped_frames;
    float fps = 1000.0 * played_frames / time_used;
    total_decode_audio_ms -= total_play_audio_ms;
    // total_decode_video_ms -= total_show_video_ms;
    Serial.printf("Played frames: %d\n", played_frames);
    Serial.printf("Skipped frames: %d (%0.1f %%)\n", skipped_frames, 100.0 * skipped_frames / total_frames);
    Serial.printf("Time used: %d ms\n", time_used);
    Serial.printf("Expected FPS: %d\n", FPS);
    Serial.printf("Actual FPS: %0.1f\n", fps);
    Serial.printf("Read audio: %lu ms (%0.1f %%)\n", total_read_audio_ms, 100.0 * total_read_audio_ms / time_used);
    Serial.printf("Decode audio: %lu ms (%0.1f %%)\n", total_decode_audio_ms, 100.0 * total_decode_audio_ms / time_used);
    Serial.printf("Play audio: %lu ms (%0.1f %%)\n", total_play_audio_ms, 100.0 * total_play_audio_ms / time_used);
    Serial.printf("Read video: %lu ms (%0.1f %%)\n", total_read_video_ms, 100.0 * total_read_video_ms / time_used);
    Serial.printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video_ms, 100.0 * total_decode_video_ms / time_used);
    Serial.printf("Show video: %lu ms (%0.1f %%)\n", total_show_video_ms, 100.0 * total_show_video_ms / time_used);
Enter fullscreen mode Exit fullscreen mode

define CHART_MARGIN 64

define LEGEND_A_COLOR 0x1BB6

define LEGEND_B_COLOR 0xFBE1

define LEGEND_C_COLOR 0x2D05

define LEGEND_D_COLOR 0xD125

define LEGEND_E_COLOR 0x9337

define LEGEND_F_COLOR 0x8AA9

define LEGEND_G_COLOR 0xE3B8

define LEGEND_H_COLOR 0x7BEF

define LEGEND_I_COLOR 0xBDE4

define LEGEND_J_COLOR 0x15F9

    // gfx->setCursor(0, 0);
    gfx->setTextColor(WHITE);
    gfx->printf("Played frames: %d\n", played_frames);
    gfx->printf("Skipped frames: %d (%0.1f %%)\n", skipped_frames, 100.0 * skipped_frames / total_frames);
    gfx->printf("Time used: %d ms\n", time_used);
    gfx->printf("Expected FPS: %d\n", FPS);
    gfx->printf("Actual FPS: %0.1f\n\n", fps);

    int16_t r1 = ((gfx->height() - CHART_MARGIN - CHART_MARGIN) / 2);
    int16_t r2 = r1 / 2;
    int16_t cx = gfx->width() - r1 - 10;
    int16_t cy = r1 + CHART_MARGIN;

    float arc_start1 = 0;
    float arc_end1 = arc_start1 + max(2.0, 360.0 * total_read_audio_ms / time_used);
    for (int i = arc_start1 + 1; i < arc_end1; i += 2)
    {
      gfx->fillArc(cx, cy, r1, r2, arc_start1 - 90.0, i - 90.0, LEGEND_A_COLOR);
    }
    gfx->fillArc(cx, cy, r1, r2, arc_start1 - 90.0, arc_end1 - 90.0, LEGEND_A_COLOR);
    gfx->setTextColor(LEGEND_A_COLOR);
    gfx->printf("Read audio: %lu ms (%0.1f %%)\n", total_read_audio_ms, 100.0 * total_read_audio_ms / time_used);

    float arc_start2 = arc_end1;
    float arc_end2 = arc_start2 + max(2.0, 360.0 * total_decode_audio_ms / time_used);
    for (int i = arc_start2 + 1; i < arc_end2; i += 2)
    {
      gfx->fillArc(cx, cy, r1, r2, arc_start2 - 90.0, i - 90.0, LEGEND_B_COLOR);
    }
    gfx->fillArc(cx, cy, r1, r2, arc_start2 - 90.0, arc_end2 - 90.0, LEGEND_B_COLOR);
    gfx->setTextColor(LEGEND_B_COLOR);
    gfx->printf("Decode audio: %lu ms (%0.1f %%)\n", total_decode_audio_ms, 100.0 * total_decode_audio_ms / time_used);
    gfx->setTextColor(LEGEND_J_COLOR);
    gfx->printf("Play audio: %lu ms (%0.1f %%)\n", total_play_audio_ms, 100.0 * total_play_audio_ms / time_used);

    float arc_start3 = arc_end2;
    float arc_end3 = arc_start3 + max(2.0, 360.0 * total_read_video_ms / time_used);
    for (int i = arc_start3 + 1; i < arc_end3; i += 2)
    {
      gfx->fillArc(cx, cy, r1, r2, arc_start3 - 90.0, i - 90.0, LEGEND_C_COLOR);
    }
    gfx->fillArc(cx, cy, r1, r2, arc_start3 - 90.0, arc_end3 - 90.0, LEGEND_C_COLOR);
    gfx->setTextColor(LEGEND_C_COLOR);
    gfx->printf("Read video: %lu ms (%0.1f %%)\n", total_read_video_ms, 100.0 * total_read_video_ms / time_used);

    float arc_start4 = arc_end3;
    float arc_end4 = arc_start4 + max(2.0, 360.0 * total_show_video_ms / time_used);
    for (int i = arc_start4 + 1; i < arc_end4; i += 2)
    {
      gfx->fillArc(cx, cy, r1, r2, arc_start4 - 90.0, i - 90.0, LEGEND_D_COLOR);
    }
    gfx->fillArc(cx, cy, r1, r2, arc_start4 - 90.0, arc_end4 - 90.0, LEGEND_D_COLOR);
    gfx->setTextColor(LEGEND_D_COLOR);
    gfx->printf("Show video: %lu ms (%0.1f %%)\n", total_show_video_ms, 100.0 * total_show_video_ms / time_used);

    float arc_start5 = 0;
    float arc_end5 = arc_start5 + max(2.0, 360.0 * total_decode_video_ms / time_used);
    for (int i = arc_start5 + 1; i < arc_end5; i += 2)
    {
      gfx->fillArc(cx, cy, r2, 0, arc_start5 - 90.0, i - 90.0, LEGEND_E_COLOR);
    }
    gfx->fillArc(cx, cy, r2, 0, arc_start5 - 90.0, arc_end5 - 90.0, LEGEND_E_COLOR);
    gfx->setTextColor(LEGEND_E_COLOR);
    gfx->printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video_ms, 100.0 * total_decode_video_ms / time_used);
  }
  // delay(60000);
Enter fullscreen mode Exit fullscreen mode

ifdef GFX_BL

  // digitalWrite(GFX_BL, LOW);
Enter fullscreen mode Exit fullscreen mode

endif

  // gfx->displayOff();
  // esp_deep_sleep_start();
}
Enter fullscreen mode Exit fullscreen mode

}
}

void loop()
{
}

include "driver/i2s.h"

include "AACDecoderHelix.h"

include "MP3DecoderHelix.h"

static unsigned long total_read_audio_ms = 0;
static unsigned long total_decode_audio_ms = 0;
static unsigned long total_play_audio_ms = 0;

static i2s_port_t _i2s_num;
static esp_err_t i2s_init(i2s_port_t i2s_num, uint32_t sample_rate,
int mck_io_num, /!< MCK in out pin. Note that ESP32 supports setting MCK on GPIO0/GPIO1/GPIO3 only/
int bck_io_num, /!< BCK in out pin/
int ws_io_num, /!< WS in out pin/
int data_out_num, /!< DATA out pin/
int data_in_num /!< DATA in pin/
)
{
_i2s_num = i2s_num;

esp_err_t ret_val = ESP_OK;

i2s_config_t i2s_config;
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
i2s_config.sample_rate = sample_rate;
i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
i2s_config.dma_buf_count = 8;
i2s_config.dma_buf_len = 160;
i2s_config.use_apll = false;
i2s_config.tx_desc_auto_clear = true;
i2s_config.fixed_mclk = 0;
i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT;
i2s_config.bits_per_chan = I2S_BITS_PER_CHAN_16BIT;

i2s_pin_config_t pin_config;
pin_config.mck_io_num = mck_io_num;
pin_config.bck_io_num = bck_io_num;
pin_config.ws_io_num = ws_io_num;
pin_config.data_out_num = data_out_num;
pin_config.data_in_num = data_in_num;

ret_val |= i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
ret_val |= i2s_set_pin(i2s_num, &pin_config);

return ret_val;
Enter fullscreen mode Exit fullscreen mode

}

static int _samprate = 0;
static void aacAudioDataCallback(AACFrameInfo &info, int16_t pwm_buffer, size_t len)
{
unsigned long s = millis();
if (_samprate != info.sampRateOut)
{
// log_i("bitRate: %d, nChans: %d, sampRateCore: %d, sampRateOut: %d, bitsPerSample: %d, outputSamps: %d, profile: %d, tnsUsed: %d, pnsUsed: %d",
// info.bitRate, info.nChans, info.sampRateCore, info.sampRateOut, info.bitsPerSample, info.outputSamps, info.profile, info.tnsUsed, info.pnsUsed);
i2s_set_clk(_i2s_num, info.sampRateOut /
sample_rate /, info.bitsPerSample / bits_cfg /, (info.nChans == 2) ? I2S_CHANNEL_STEREO : I2S_CHANNEL_MONO / channel /);
_samprate = info.sampRateOut;
}
size_t i2s_bytes_written = 0;
i2s_write(_i2s_num, pwm_buffer, len * 2, &i2s_bytes_written, portMAX_DELAY);
// log_i("len: %d, i2s_bytes_written: %d", len, i2s_bytes_written);
total_play_audio_ms += millis() - s;
}
static void mp3AudioDataCallback(MP3FrameInfo &info, int16_t *pwm_buffer, size_t len)
{
unsigned long s = millis();
if (_samprate != info.samprate)
{
log_i("bitrate: %d, nChans: %d, samprate: %d, bitsPerSample: %d, outputSamps: %d, layer: %d, version: %d",
info.bitrate, info.nChans, info.samprate, info.bitsPerSample, info.outputSamps, info.layer, info.version);
i2s_set_clk(_i2s_num, info.samprate /
sample_rate /, info.bitsPerSample / bits_cfg /, (info.nChans == 2) ? I2S_CHANNEL_STEREO : I2S_CHANNEL_MONO / channel */);
_samprate = info.samprate;
}
size_t i2s_bytes_written = 0;
i2s_write(_i2s_num, pwm_buffer, len * 2, &i2s_bytes_written, portMAX_DELAY);
// log_i("len: %d, i2s_bytes_written: %d", len, i2s_bytes_written);
total_play_audio_ms += millis() - s;
}

static uint8_t _frame[MP3_MAX_FRAME_SIZE]; // MP3_MAX_FRAME_SIZE is smaller, so always use MP3_MAX_FRAME_SIZE

static libhelix::AACDecoderHelix _aac(aacAudioDataCallback);
static void aac_player_task(void *pvParam)
{
Stream *input = (Stream *)pvParam;

int r, w;
unsigned long ms = millis();
while (r = input->readBytes(_frame, MP3_MAX_FRAME_SIZE))
{
    total_read_audio_ms += millis() - ms;
    ms = millis();

    while (r > 0)
    {
        w = _aac.write(_frame, r);
        // log_i("r: %d, w: %d\n", r, w);
        r -= w;
    }
    total_decode_audio_ms += millis() - ms;
    ms = millis();
}
log_i("AAC stop.");

vTaskDelete(NULL);
Enter fullscreen mode Exit fullscreen mode

}

static libhelix::MP3DecoderHelix _mp3(mp3AudioDataCallback);
static void mp3_player_task(void *pvParam)
{
Stream *input = (Stream *)pvParam;

int r, w;
unsigned long ms = millis();
while (r = input->readBytes(_frame, MP3_MAX_FRAME_SIZE))
{
    total_read_audio_ms += millis() - ms;
    ms = millis();

    while (r > 0)
    {
        w = _mp3.write(_frame, r);
        // log_i("r: %d, w: %d\n", r, w);
        r -= w;
    }
    total_decode_audio_ms += millis() - ms;
    ms = millis();
}
log_i("MP3 stop.");

vTaskDelete(NULL);
Enter fullscreen mode Exit fullscreen mode

}

static BaseType_t aac_player_task_start(Stream *input, BaseType_t audioAssignCore)
{
_aac.begin();

return xTaskCreatePinnedToCore(
    (TaskFunction_t)aac_player_task,
    (const char *const)"AAC Player Task",
    (const uint32_t)2000,
    (void *const)input,
    (UBaseType_t)configMAX_PRIORITIES - 1,
    (TaskHandle_t *const)NULL,
    (const BaseType_t)audioAssignCore);
Enter fullscreen mode Exit fullscreen mode

}

static BaseType_t mp3_player_task_start(Stream *input, BaseType_t audioAssignCore)
{
_mp3.begin();

return xTaskCreatePinnedToCore(
    (TaskFunction_t)mp3_player_task,
    (const char *const)"MP3 Player Task",
    (const uint32_t)2000,
    (void *const)input,
    (UBaseType_t)configMAX_PRIORITIES - 1,
    (TaskHandle_t *const)NULL,
    (const BaseType_t)audioAssignCore);
Enter fullscreen mode Exit fullscreen mode

}

define READ_BUFFER_SIZE 1024

// #define MAXOUTPUTSIZE (MAX_BUFFERED_PIXELS / 16 / 16)

define MAXOUTPUTSIZE (288 / 3 / 16)

define NUMBER_OF_DECODE_BUFFER 3

define NUMBER_OF_DRAW_BUFFER 9

include

include

typedef struct
{
int32_t size;
uint8_t *buf;
} mjpegBuf;

typedef struct
{
xQueueHandle xqh;
JPEG_DRAW_CALLBACK *drawFunc;
} paramDrawTask;

typedef struct
{
xQueueHandle xqh;
mjpegBuf *mBuf;
JPEG_DRAW_CALLBACK *drawFunc;
} paramDecodeTask;

static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER];
static int _draw_queue_cnt = 0;
static JPEGDEC _jpegDec;
static xQueueHandle _xqh;
static bool _useBigEndian;

static unsigned long total_read_video_ms = 0;
static unsigned long total_decode_video_ms = 0;
static unsigned long total_show_video_ms = 0;

Stream *_input;

int32_t _mjpegBufSize;

uint8_t *_read_buf;
int32_t _mjpeg_buf_offset = 0;

TaskHandle_t _decodeTask;
TaskHandle_t _draw_task;
paramDecodeTask _pDecodeTask;
paramDrawTask _pDrawTask;
uint8_t *_mjpeg_buf;
uint8_t _mBufIdx = 0;

int32_t _inputindex = 0;
int32_t _buf_read;
int32_t _remain = 0;
mjpegBuf _mjpegBufs[NUMBER_OF_DECODE_BUFFER];

static int queueDrawMCU(JPEGDRAW *pDraw)
{
int len = pDraw->iWidth * pDraw->iHeight * 2;
JPEGDRAW *j = &jpegdraws[_draw_queue_cnt % NUMBER_OF_DRAW_BUFFER];
j->x = pDraw->x;
j->y = pDraw->y;
j->iWidth = pDraw->iWidth;
j->iHeight = pDraw->iHeight;
memcpy(j->pPixels, pDraw->pPixels, len);

// log_i("queueDrawMCU start.");
++_draw_queue_cnt;
xQueueSend(_xqh, &j, portMAX_DELAY);
// log_i("queueDrawMCU end.");

return 1;
}

static void decode_task(void *arg)
{
paramDecodeTask *p = (paramDecodeTask *)arg;
mjpegBuf *mBuf;
log_i("decode_task start.");
while (xQueueReceive(p->xqh, &mBuf, portMAX_DELAY))
{
// log_i("mBuf->size: %d", mBuf->size);
// log_i("mBuf->buf start: %X %X, end: %X, %X.", mBuf->buf[0], mBuf->buf[1], mBuf->buf[mBuf->size - 2], mBuf->buf[mBuf->size - 1]);
unsigned long s = millis();

_jpegDec.openRAM(mBuf->buf, mBuf->size, p->drawFunc);

// _jpegDec.setMaxOutputSize(MAXOUTPUTSIZE);
if (_useBigEndian)
{
  _jpegDec.setPixelType(RGB565_BIG_ENDIAN);
}
_jpegDec.setMaxOutputSize(MAXOUTPUTSIZE);
_jpegDec.decode(0, 0, 0);
_jpegDec.close();

total_decode_video_ms += millis() - s;
Enter fullscreen mode Exit fullscreen mode

}
vQueueDelete(p->xqh);
log_i("decode_task end.");
vTaskDelete(NULL);
}

static void draw_task(void *arg)
{
paramDrawTask *p = (paramDrawTask *)arg;
JPEGDRAW *pDraw;
log_i("draw_task start.");
while (xQueueReceive(p->xqh, &pDraw, portMAX_DELAY))
{
// log_i("draw_task work start: x: %d, y: %d, iWidth: %d, iHeight: %d.", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
p->drawFunc(pDraw);
// log_i("draw_task work end.");
}
vQueueDelete(p->xqh);
log_i("draw_task end.");
vTaskDelete(NULL);
}

bool mjpeg_setup(Stream *input, int32_t mjpegBufSize, JPEG_DRAW_CALLBACK *pfnDraw,
bool useBigEndian, BaseType_t decodeAssignCore, BaseType_t drawAssignCore)
{
_input = input;
_mjpegBufSize = mjpegBufSize;
_useBigEndian = useBigEndian;

for (int i = 0; i < NUMBER_OF_DECODE_BUFFER; ++i)
{
_mjpegBufs[i].buf = (uint8_t *)malloc(mjpegBufSize);
if (_mjpegBufs[i].buf)
{
log_i("#%d decode buffer allocated.", i);
}
else
{
log_e("#%d decode buffer allocat failed.", i);
}
}
_mjpeg_buf = _mjpegBufs[_mBufIdx].buf;

if (!_read_buf)
{
_read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE);
}
if (_read_buf)
{
log_i("Read buffer allocated.");
}

_xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW));
_pDrawTask.xqh = _xqh;
_pDrawTask.drawFunc = pfnDraw;
_pDecodeTask.xqh = xQueueCreate(NUMBER_OF_DECODE_BUFFER, sizeof(mjpegBuf));
_pDecodeTask.drawFunc = queueDrawMCU;

xTaskCreatePinnedToCore(
(TaskFunction_t)decode_task,
(const char *const)"MJPEG decode Task",
(const uint32_t)2000,
(void *const)&_pDecodeTask,
(UBaseType_t)configMAX_PRIORITIES - 1,
(TaskHandle_t *const)&_decodeTask,
(const BaseType_t)decodeAssignCore);
xTaskCreatePinnedToCore(
(TaskFunction_t)draw_task,
(const char *const)"MJPEG Draw Task",
(const uint32_t)2000,
(void *const)&_pDrawTask,
(UBaseType_t)configMAX_PRIORITIES - 1,
(TaskHandle_t *const)&_draw_task,
(const BaseType_t)drawAssignCore);

for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++)
{
if (!jpegdraws[i].pPixels)
{
jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA);
}
if (jpegdraws[i].pPixels)
{
log_i("#%d draw buffer allocated.", i);
}
else
{
log_e("#%d draw buffer allocat failed.", i);
}
}

return true;
}

bool mjpeg_read_frame()
{
if (_inputindex == 0)
{
_buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE);
_inputindex += _buf_read;
}
_mjpeg_buf_offset = 0;
int i = 0;
bool found_FFD8 = false;
while ((_buf_read > 0) && (!found_FFD8))
{
i = 0;
while ((i < _buf_read) && (!found_FFD8))
{
if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header
{
// log_i("Found FFD8 at: %d.", i);
found_FFD8 = true;
}
++i;
}
if (found_FFD8)
{
--i;
}
else
{
_buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE);
}
}
uint8_t *_p = _read_buf + i;
_buf_read -= i;
bool found_FFD9 = false;
if (_buf_read > 0)
{
i = 3;
while ((_buf_read > 0) && (!found_FFD9))
{
if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer
{
found_FFD9 = true;
}
else
{
while ((i < _buf_read) && (!found_FFD9))
{
if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer
{
found_FFD9 = true;
++i;
}
++i;
}
}

  // log_i("i: %d", i);
  memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i);
  _mjpeg_buf_offset += i;
  int32_t o = _buf_read - i;
  if (o > 0)
  {
    // log_i("o: %d", o);
    memcpy(_read_buf, _p + i, o);
    _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o);
    _p = _read_buf;
    _inputindex += _buf_read;
    _buf_read += o;
    // log_i("_buf_read: %d", _buf_read);
  }
  else
  {
    _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE);
    _p = _read_buf;
    _inputindex += _buf_read;
  }
  i = 0;
}
if (found_FFD9)
{
  // log_i("Found FFD9 at: %d.", _mjpeg_buf_offset);
  if (_mjpeg_buf_offset > _mjpegBufSize) {
    log_e("_mjpeg_buf_offset(%d) > _mjpegBufSize (%d)", _mjpeg_buf_offset, _mjpegBufSize);
  }
  return true;
}
Enter fullscreen mode Exit fullscreen mode

}

return false;
}

bool mjpeg_draw_frame()
{
mjpegBuf *mBuf = &_mjpegBufs[_mBufIdx];
mBuf->size = _mjpeg_buf_offset;
// log_i("_mjpegBufs[%d].size: %d.", _mBufIdx, _mjpegBufs[_mBufIdx].size);
// log_i("_mjpegBufs[%d].buf start: %X %X, end: %X, %X.", _mjpegBufs, _mjpegBufs[_mBufId].buf[0], _mjpegBufs[_mBufIdx].buf[1], _mjpegBufs[_mBufIdx].buf[_mjpeg_buf_offset - 2], _mjpegBufs[_mBufIdx].buf[_mjpeg_buf_offset - 1]);
xQueueSend(_pDecodeTask.xqh, &mBuf, portMAX_DELAY);
++_mBufIdx;
if (_mBufIdx >= NUMBER_OF_DECODE_BUFFER)
{
_mBufIdx = 0;
}
_mjpeg_buf = _mjpegBufs[_mBufIdx].buf;
// log_i("queue decode_task end");

return true;
}
//include libraries:

include "LedControl.h"

include // Font library

include // DS1307 clock

include "RTClib.h" // DS1307 clock

include // Button library by Alexander Brevig

// Setup LED Matrix

// pin 10 is connected to the DataIn on the display
// pin 12 is connected to the CLK on the display
// pin 11 is connected to LOAD on the display(cs)
LedControl lc = LedControl(10, 12, 11, 4); //sets the 3 pins as 12, 11 & 10 and then sets 4 displays (max is 8 displays)

//global variables
byte intensity = 7; // Default intensity/brightness (0-15)
byte clock_mode = 0; // Default clock mode. Default = 0 (basic_mode)
bool random_mode = 0; // Define random mode - changes the display type every few hours. Default = 0 (off)
byte old_mode = clock_mode; // Stores the previous clock mode, so if we go to date or whatever, we know what mode to go back to after.
bool ampm = 0; // Define 12 or 24 hour time. 0 = 24 hour. 1 = 12 hour
byte change_mode_time = 0; // Holds hour when clock mode will next change if in random mode.
unsigned long delaytime = 500; // We always wait a bit between updates of the display
int rtc[7]; // Holds real time clock output

char days[7][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
}; //day array - used in slide, basic_mode and jumble modes (The DS1307 outputs 1-7 values for day of week)
char daysfull[7][9] = {
"Sunday", "Monday", "Tuesday", "Wed", "Thursday", "Friday", "Saturday"
};
char suffix[4][3] = {
"st", "nd", "rd", "th"
}; //date suffix array, used in slide, basic_mode and jumble modes. e,g, 1st 2nd ...

//define constants

define NUM_DISPLAY_MODES 3 // Number display modes (conting zero as the first mode)

define NUM_SETTINGS_MODES 4 // Number settings modes = 6 (conting zero as the first mode)

define SLIDE_DELAY 20 // The time in milliseconds for the slide effect per character in slide mode. Make this higher for a slower effect

define cls clear_display // Clear display

RTC_DS1307 ds1307; // Create RTC object

Button buttonA = Button(2, BUTTON_PULLUP); // Setup button A (using button library)
Button buttonB = Button(3, BUTTON_PULLUP); // Setup button B (using button library)

void setup() {

digitalWrite(2, HIGH); // turn on pullup resistor for button on pin 2
digitalWrite(3, HIGH); // turn on pullup resistor for button on pin 3
digitalWrite(4, HIGH); // turn on pullup resistor for button on pin 4

Serial.begin(9600); //start serial

//initialize the 4 matrix panels
//we have already set the number of devices when we created the LedControl
int devices = lc.getDeviceCount();
//we have to init all devices in a loop
for (int address = 0; address < devices; address++) {
/The MAX72XX is in power-saving mode on startup/
lc.shutdown(address, false);
/* Set the brightness to a medium values /
lc.setIntensity(address, intensity);
/
and clear the display */
lc.clearDisplay(address);
}

//Setup DS1307 RTC

ifdef AVR

Wire.begin();

else

Wire1.begin(); // Shield I2C pins connect to alt I2C bus on Arduino

endif

ds1307.begin(); //start RTC Clock

if (! ds1307.isrunning()) {
Serial.println("RTC is NOT running!");
ds1307.adjust(DateTime(DATE, TIME)); // sets the RTC to the date & time this sketch was compiled
}
//Show software version & hello message
printver();

//enable red led
digitalWrite(13, HIGH);
}

void loop() {

//run the clock with whatever mode is set by clock_mode - the default is set at top of code.
switch (clock_mode){

case 0:
basic_mode();
break;
case 1:
small_mode();
break;
case 2:
slide();
break;
case 3:
word_clock();
break;
case 4:
setup_menu();
break;
}
}

//plot a point on the display
void plot (byte x, byte y, byte val) {

//select which matrix depending on the x coord
byte address;
if (x >= 0 && x <= 7) {
address = 3;
}
if (x >= 8 && x <= 15) {
address = 2;
x = x - 8;
}
if (x >= 16 && x <= 23) {
address = 1;
x = x - 16;
}
if (x >= 24 && x <= 31) {
address = 0;
x = x - 24;
}

if (val == 1) {
// lc.setLed(address, y, x, true);
lc.setLed(address, 7-y, x, true);
} else {
// lc.setLed(address, y, x, false);
lc.setLed(address, 7-y, x, false);
}
}

//clear screen
void clear_display() {
for (byte address = 0; address < 4; address++) {
lc.clearDisplay(address);
}
}

//fade screen down
void fade_down() {

//fade from global intensity to 1
for (byte i = intensity; i > 0; i--) {
for (byte address = 0; address < 4; address++) {
lc.setIntensity(address, i);
}
delay(30); //change this to change fade down speed
}

clear_display(); //clear display completely (off)

//reset intentsity to global val
for (byte address = 0; address < 4; address++) {
lc.setIntensity(address, intensity);
}
}

//power up led test & display software version number
void printver() {

byte i = 0;
char ver_a[9] = "Vers 1.0";
char ver_b[9] = " Hello! ";

//test all leds.
for (byte x = 0; x <= 31; x++) {
for (byte y = 0; y <= 7; y++) {
plot(x, y, 1);
}
}
delay(500);
fade_down();

while (ver_a[i]) {
puttinychar((i * 4), 1, ver_a[i]);
delay(35);
i++;
}
delay(700);
fade_down();
i = 0;
while (ver_b[i]) {
puttinychar((i * 4), 1, ver_b[i]);
delay(35);
i++;
}
delay(700);
fade_down();
}

// puttinychar
// Copy a 3x5 character glyph from the myfont data structure to display memory, with its upper left at the given coordinate
// This is unoptimized and simply uses plot() to draw each dot.
void puttinychar(byte x, byte y, char c)
{
byte dots;
if (c >= 'A' && c <= 'Z' || (c >= 'a' && c <= 'z') ) {
c &= 0x1F; // A-Z maps to 1-26
}
else if (c >= '0' && c <= '9') {
c = (c - '0') + 32;
}
else if (c == ' ') {
c = 0; // space
}
else if (c == '.') {
c = 27; // full stop
}
else if (c == ':') {
c = 28; // colon
}
else if (c == '\'') {
c = 29; // single quote mark
}
else if (c == '!') {
c = 30; // single quote mark
}
else if (c == '?') {
c = 31; // single quote mark
}

for (byte col = 0; col < 3; col++) {
dots = pgm_read_byte_near(&mytinyfont[c][col]);
for (char row = 0; row < 5; row++) {
if (dots & (16 >> row))
plot(x + col, y + row, 1);
else
plot(x + col, y + row, 0);
}
}
}

void putnormalchar(byte x, byte y, char c)
{

byte dots;
// if (c >= 'A' && c <= 'Z' || (c >= 'a' && c <= 'z') ) {
// c &= 0x1F; // A-Z maps to 1-26
// }
if (c >= 'A' && c <= 'Z' ) {
c &= 0x1F; // A-Z maps to 1-26
}
else if (c >= 'a' && c <= 'z') {
c = (c - 'a') + 41; // A-Z maps to 41-67
}
else if (c >= '0' && c <= '9') {
c = (c - '0') + 31;
}
else if (c == ' ') {
c = 0; // space
}
else if (c == '.') {
c = 27; // full stop
}
else if (c == '\'') {
c = 28; // single quote mark
}
else if (c == ':') {
c = 29; // clock_mode selector arrow
}
else if (c == '>') {
c = 30; // clock_mode selector arrow
}
else if (c >= -80 && c <= -67) {
c *= -1;
}

for (char col = 0; col < 5; col++) {
dots = pgm_read_byte_near(&myfont[c][col]);
for (char row = 0; row < 7; row++) {
//check coords are on screen before trying to plot
//if ((x >= 0) && (x <= 31) && (y >= 0) && (y <= 7)){

  if (dots & (64 >> row)) {   // only 7 rows.
    plot(x + col, y + row, 1);
  } else {
    plot(x + col, y + row, 0);
  }
  //}
}
Enter fullscreen mode Exit fullscreen mode

}
}

//small_mode
//show the time in small 3x5 characters with seconds display

void small_mode() {

char textchar[8]; // the 16 characters on the display
byte mins = 100; //mins
byte secs = rtc[0]; //seconds
byte old_secs = secs; //holds old seconds value - from last time seconds were updated o display - used to check if seconds have changed

cls();

//run clock main loop as long as run_mode returns true
while (run_mode()) {

get_time();

//check for button press
if (buttonA.uniquePress()) {
  switch_mode();
  return;
}
if (buttonB.uniquePress()) {
  display_date();
  return;
}

//if secs changed then update them on the display
secs = rtc[0];
if (secs != old_secs) {

  //secs
  char buffer[3];
  itoa(secs, buffer, 10);

  //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa coverts this to chars with space "3 ".
  if (secs < 10) {
    buffer[1] = buffer[0];
    buffer[0] = '0';
  }

  puttinychar( 20, 1, ':'); //seconds colon
  puttinychar( 24, 1, buffer[0]); //seconds
  puttinychar( 28, 1, buffer[1]); //seconds
  old_secs = secs;
}

//if minute changes change time
if (mins != rtc[1]) {

  //reset these for comparison next time
  mins = rtc[1];
  byte hours = rtc[2];
  if (hours > 12) {
    hours = hours - ampm * 12;
  }
  if (hours < 1) {
    hours = hours + ampm * 12;
  }


  //byte dow  = rtc[3]; // the DS1307 outputs 0 - 6 where 0 = Sunday0 - 6 where 0 = Sunday.
  //byte date = rtc[4];

  //set characters
  char buffer[3];
  itoa(hours, buffer, 10);

  //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa coverts this to chars with space "3 ".
  if (hours < 10) {
    buffer[1] = buffer[0];
    //if we are in 12 hour mode blank the leading zero.
    if (ampm) {
      buffer[0] = ' ';
    }
    else {
      buffer[0] = '0';
    }
  }
  //set hours chars
  textchar[0] = buffer[0];
  textchar[1] = buffer[1];
  textchar[2] = ':';

  itoa (mins, buffer, 10);
  if (mins < 10) {
    buffer[1] = buffer[0];
    buffer[0] = '0';
  }
  //set mins characters
  textchar[3] = buffer[0];
  textchar[4] = buffer[1];

  //do seconds
  textchar[5] = ':';
  buffer[3];
  secs = rtc[0];
  itoa(secs, buffer, 10);

  //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa coverts this to chars with space "3 ".
  if (secs < 10) {
    buffer[1] = buffer[0];
    buffer[0] = '0';
  }
  //set seconds
  textchar[6] = buffer[0];
  textchar[7] = buffer[1];

  byte x = 0;
  byte y = 0;

  //print each char
  for (byte x = 0; x < 6 ; x++) {
    puttinychar( x * 4, 1, textchar[x]);
  }
}
delay(50);
Enter fullscreen mode Exit fullscreen mode

}
fade_down();
}

// basic_mode()
// show the time in 5x7 characters
void basic_mode()
{
cls();

char buffer[3]; //for int to char conversion to turn rtc values into chars we can print on screen
byte offset = 0; //used to offset the x postition of the digits and centre the display when we are in 12 hour mode and the clock shows only 3 digits. e.g. 3:21
byte x, y; //used to draw a clear box over the left hand "1" of the display when we roll from 12:59 -> 1:00am in 12 hour mode.

//do 12/24 hour conversion if ampm set to 1
byte hours = rtc[2];

if (hours > 12) {
hours = hours - ampm * 12;
}
if (hours < 1) {
hours = hours + ampm * 12;
}

//do offset conversion
if (ampm && hours < 10) {
offset = 2;
}

//set the next minute we show the date at
//set_next_date();

// initially set mins to value 100 - so it wll never equal rtc[1] on the first loop of the clock, meaning we draw the clock display when we enter the function
byte secs = 100;
byte mins = 100;
int count = 0;

//run clock main loop as long as run_mode returns true
while (run_mode()) {

//get the time from the clock chip
get_time();

//check for button press
if (buttonA.uniquePress()) {
  switch_mode();
  return;
}
if (buttonB.uniquePress()) {
  display_date();
  return;
}

//check whether it's time to automatically display the date
//check_show_date();

//draw the flashing : as on if the secs have changed.
if (secs != rtc[0]) {

  //update secs with new value
  secs = rtc[0];

  //draw :
  plot (15 - offset, 2, 1); //top point
  plot (15 - offset, 5, 1); //bottom point
  count = 400;
}

//if count has run out, turn off the :
if (count == 0) {
  plot (15 - offset, 2, 0); //top point
  plot (15 - offset, 5, 0); //bottom point
}
else {
  count--;
}

//re draw the display if button pressed or if mins != rtc[1] i.e. if the time has changed from what we had stored in mins, (also trigggered on first entering function when mins is 100)
if (mins != rtc[1]) {

  //update mins and hours with the new values
  mins = rtc[1];
  hours = rtc[2];

  //adjust hours of ampm set to 12 hour mode
  if (hours > 12) {
    hours = hours - ampm * 12;
  }
  if (hours < 1) {
    hours = hours + ampm * 12;
  }

  itoa(hours, buffer, 10);

  //if hours < 10 the num e.g. "3" hours, itoa coverts this to chars with space "3 " which we dont want
  if (hours < 10) {
    buffer[1] = buffer[0];
    buffer[0] = '0';
  }

  //print hours
  //if we in 12 hour mode and hours < 10, then don't print the leading zero, and set the offset so we centre the display with 3 digits.
  if (ampm && hours < 10) {
    offset = 2;

    //if the time is 1:00am clear the entire display as the offset changes at this time and we need to blank out the old 12:59
    if ((hours == 1 && mins == 0) ) {
      cls();
    }
  }
  else {
    //else no offset and print hours tens digit
    offset = 0;

    //if the time is 10:00am clear the entire display as the offset changes at this time and we need to blank out the old 9:59
    if (hours == 10 && mins == 0) {
      cls();
    }


    putnormalchar(1,  0, buffer[0]);
  }
  //print hours ones digit
  putnormalchar(7 - offset, 0, buffer[1]);


  //print mins
  //add leading zero if mins < 10
  itoa (mins, buffer, 10);
  if (mins < 10) {
    buffer[1] = buffer[0];
    buffer[0] = '0';
  }
  //print mins tens and ones digits
  putnormalchar(19 - offset, 0, buffer[0]);
  putnormalchar(25 - offset, 0, buffer[1]);
}
Enter fullscreen mode Exit fullscreen mode

}
fade_down();
}

//like basic_mode but with slide effect
void slide() {

byte digits_old[4] = {99, 99, 99, 99}; //old values we store time in. Set to somthing that will never match the time initially so all digits get drawn wnen the mode starts
byte digits_new[4]; //new digits time will slide to reveal
byte digits_x_pos[4] = {25, 19, 7, 1}; //x pos for which to draw each digit at

char old_char[2]; //used when we use itoa to transpose the current digit (type byte) into a char to pass to the animation function
char new_char[2]; //used when we use itoa to transpose the new digit (type byte) into a char to pass to the animation function

//old_chars - stores the 5 day and date suffix chars on the display. e.g. "mon" and "st". We feed these into the slide animation as the current char when these chars are updated.
//We sent them as A initially, which are used when the clocl enters the mode and no last chars are stored.
//char old_chars[6] = "AAAAA";

//plot the clock colon on the display
cls();
putnormalchar( 13, 0, ':');

byte old_secs = rtc[0]; //store seconds in old_secs. We compare secs and old secs. WHen they are different we redraw the display

//run clock main loop as long as run_mode returns true
while (run_mode()) {

get_time();

//check for button press
if (buttonA.uniquePress()) {
  switch_mode();
  return;
}
  if (buttonB.uniquePress()) {
  display_date();
  return;
}

//if secs have changed then update the display
if (rtc[0] != old_secs) {
  old_secs = rtc[0];

  //do 12/24 hour conversion if ampm set to 1
  byte hours = rtc[2];
  if (hours > 12) {
    hours = hours - ampm * 12;
  }
  if (hours < 1) {
    hours = hours + ampm * 12;
  }

  //split all date and time into individual digits - stick in digits_new array

  //rtc[0] = secs                        //array pos and digit stored
  //digits_new[0] = (rtc[0]%10);           //0 - secs ones
  //digits_new[1] = ((rtc[0]/10)%10);      //1 - secs tens
  //rtc[1] = mins
  digits_new[0] = (rtc[1] % 10);         //2 - mins ones
  digits_new[1] = ((rtc[1] / 10) % 10);  //3 - mins tens
  //rtc[2] = hours
  digits_new[2] = (hours % 10);         //4 - hour ones
  digits_new[3] = ((hours / 10) % 10);  //5 - hour tens
  //rtc[4] = date
  //digits_new[6] = (rtc[4]%10);           //6 - date ones
  //digits_new[7] = ((rtc[4]/10)%10);      //7 - date tens

  //draw initial screen of all chars. After this we just draw the changes.

  //compare digits 0 to 3 (mins and hours)
  for (byte i = 0; i <= 3; i++) {
    //see if digit has changed...
    if (digits_old[i] != digits_new[i]) {

      //run 9 step animation sequence for each in turn
      for (byte seq = 0; seq <= 8 ; seq++) {

        //convert digit to string
        itoa(digits_old[i], old_char, 10);
        itoa(digits_new[i], new_char, 10);

        //if set to 12 hour mode and we're on digit 2 (hours tens mode) then check to see if this is a zero. If it is, blank it instead so we get 2.00pm not 02.00pm
        if (ampm && i == 3) {
          if (digits_new[3] == 0) {
            new_char[0] = ' ';
          }
          if (digits_old[3] == 0) {
            old_char[0] = ' ';
          }
        }
        //draw the animation frame for each digit
        slideanim(digits_x_pos[i], 0, seq, old_char[0], new_char[0]);
        delay(SLIDE_DELAY);
      }
    }
  }

  /*
  //compare date digit 6 (ones) and (7) tens - if either of these change we need to update the date line. We compare date tens as say from Jan 31 -> Feb 01 then ones digit doesn't change
  if ((digits_old[6] != digits_new[6]) || (digits_old[7] != digits_new[7])) {
    //change the day shown. Loop below goes through each of the 3 chars in turn e.g. "MON"
    for (byte day_char = 0; day_char <=2 ; day_char++){
      //run the anim sequence for each char
      for (byte seq = 0; seq <=8 ; seq++){
        //the day (0 - 6) Read this number into the days char array. the seconds number in the array 0-2 gets the 3 chars of the day name, e.g. m o n
        slideanim(6*day_char,8,seq,old_chars[day_char],days[rtc[3]][day_char]); //6 x day_char gives us the x pos for the char
        delay(SLIDE_DELAY);
      }
      //save the old day chars into the old_chars array at array pos 0-2. We use this next time we change the day and feed it to the animation as the current char. The updated char is fed in as the new char.
      old_chars[day_char] = days[rtc[3]][day_char];
    }

    //change the date tens digit (if needed) and ones digit. (the date ones digit wil alwaus change, but putting this in the 'if' loop makes it a bit neater code wise.)
    for (byte i = 7; i >= 6; i--){
      if (digits_old[i] != digits_new[i]) {
        for (byte seq = 0; seq <=8 ; seq++){
          itoa(digits_old[i],old_char,10);
          itoa(digits_new[i],new_char,10);
          slideanim(digits_x_pos[i],8,seq,old_char[0],new_char[0]);
          delay(SLIDE_DELAY);
        }
      }
    }

    //print the day suffix "nd" "rd" "th" etc. First work out date 2 letter suffix - eg st, nd, rd, th
    byte s = 3; //the pos to read our suffix array from.
    byte date = rtc[4];
    if(date == 1 || date == 21 || date == 31) {
      s = 0;
    }
    else if (date == 2 || date == 22) {
      s = 1;
    }
    else if (date == 3 || date == 23) {
      s = 2;
    }

    for (byte suffix_char = 0; suffix_char <=1 ; suffix_char++){
      for (byte seq = 0; seq <=8 ; seq++){
        slideanim((suffix_char*6)+36,8,seq,old_chars[suffix_char+3],suffix[s][suffix_char]); // we pass in the old_char array char as the current char and the suffix array as the new char
        delay(SLIDE_DELAY);
      }
      //save the suffic char in the old chars array at array pos 3 and 5.  We use these chars next time we change the suffix and feed it to the animation as the current char. The updated char is fed in as the new char.
      old_chars[suffix_char+3] = suffix[s][suffix_char];
    }
  }//end do date line
  */


  //save digita array tol old for comparison next loop
  for (byte i = 0; i <= 3; i++) {
    digits_old[i] =  digits_new[i];
  }
}//secs/oldsecs
Enter fullscreen mode Exit fullscreen mode

}//while loop
fade_down();
}

//called by slide
//this draws the animation of one char sliding on and the other sliding off. There are 8 steps in the animation, we call the function to draw one of the steps from 0-7
//inputs are are char x and y, animation frame sequence (0-7) and the current and new chars being drawn.
void slideanim(byte x, byte y, byte sequence, char current_c, char new_c) {

// To slide one char off and another on we need 9 steps or frames in sequence...

// seq# 0123456 <-rows of the display
// | |||||||
// seq0 0123456 START - all rows of the display 0-6 show the current characters rows 0-6
// seq1 012345 current char moves down one row on the display. We only see it's rows 0-5. There are at display positions 1-6 There is a blank row inserted at the top
// seq2 6 01234 current char moves down 2 rows. we now only see rows 0-4 at display rows 2-6 on the display. Row 1 of the display is blank. Row 0 shows row 6 of the new char
// seq3 56 0123
// seq4 456 012 half old / half new char
// seq5 3456 01
// seq6 23456 0
// seq7 123456
// seq8 0123456 END - all rows show the new char

//from above we can see...
//currentchar runs 0-6 then 0-5 then 0-4 all the way to 0. starting Y position increases by 1 row each time.
//new char runs 6 then 5-6 then 4-6 then 3-6. starting Y position increases by 1 row each time.

//if sequence number is below 7, we need to draw the current char
if (sequence < 7) {
byte dots;
// if (current_c >= 'A' && || (current_c >= 'a' && current_c <= 'z') ) {
// current_c &= 0x1F; // A-Z maps to 1-26
// }
if (current_c >= 'A' && current_c <= 'Z' ) {
current_c &= 0x1F; // A-Z maps to 1-26
}
else if (current_c >= 'a' && current_c <= 'z') {
current_c = (current_c - 'a') + 41; // A-Z maps to 41-67
}
else if (current_c >= '0' && current_c <= '9') {
current_c = (current_c - '0') + 31;
}
else if (current_c == ' ') {
current_c = 0; // space
}
else if (current_c == '.') {
current_c = 27; // full stop
}
else if (current_c == '\'') {
current_c = 28; // single quote mark
}
else if (current_c == ':') {
current_c = 29; //colon
}
else if (current_c == '>') {
current_c = 30; // clock_mode selector arrow
}

byte curr_char_row_max = 7 - sequence; //the maximum number of rows to draw is 6 - sequence number
byte start_y = sequence; //y position to start at - is same as sequence number. We inc this each loop

//plot each row up to row maximum (calculated from sequence number)
for (byte curr_char_row = 0; curr_char_row <= curr_char_row_max; curr_char_row++) {
  for (byte col = 0; col < 5; col++) {
    dots = pgm_read_byte_near(&myfont[current_c][col]);
    if (dots & (64 >> curr_char_row))
      plot(x + col, y + start_y, 1); //plot led on
    else
      plot(x + col, y + start_y, 0); //else plot led off
  }
  start_y++;//add one to y so we draw next row one down
}
Enter fullscreen mode Exit fullscreen mode

}

//draw a blank line between the characters if sequence is between 1 and 7. If we don't do this we get the remnants of the current chars last position left on the display
if (sequence >= 1 && sequence <= 8) {
for (byte col = 0; col < 5; col++) {
plot(x + col, y + (sequence - 1), 0); //the y position to draw the line is equivalent to the sequence number - 1
}
}

//if sequence is above 2, we also need to start drawing the new char
if (sequence >= 2) {

//work out char
byte dots;
//if (new_c >= 'A' && new_c <= 'Z' || (new_c >= 'a' && new_c <= 'z') ) {
//  new_c &= 0x1F;   // A-Z maps to 1-26
//}
if (new_c >= 'A' && new_c <= 'Z' ) {
  new_c &= 0x1F;   // A-Z maps to 1-26
}
else if (new_c >= 'a' && new_c <= 'z') {
  new_c = (new_c - 'a') + 41;   // A-Z maps to 41-67
}
else if (new_c >= '0' && new_c <= '9') {
  new_c = (new_c - '0') + 31;
}
else if (new_c == ' ') {
  new_c = 0; // space
}
else if (new_c == '.') {
  new_c = 27; // full stop
}
else if (new_c == '\'') {
  new_c = 28; // single quote mark
}
else if (new_c == ':') {
  new_c = 29; // clock_mode selector arrow
}
else if (new_c == '>') {
  new_c = 30; // clock_mode selector arrow
}

byte newcharrowmin = 6 - (sequence - 2); //minimumm row num to draw for new char - this generates an output of 6 to 0 when fed sequence numbers 2-8. This is the minimum row to draw for the new char
byte start_y = 0; //y position to start at - is same as sequence number. we inc it each row

//plot each row up from row minimum (calculated by sequence number) up to 6
for (byte newcharrow = newcharrowmin; newcharrow <= 6; newcharrow++) {
  for (byte col = 0; col < 5; col++) {
    dots = pgm_read_byte_near(&myfont[new_c][col]);
    if (dots & (64 >> newcharrow))
      plot(x + col, y + start_y, 1); //plot led on
    else
      plot(x + col, y + start_y, 0); //else plot led off
  }
  start_y++;//add one to y so we draw next row one down
}
Enter fullscreen mode Exit fullscreen mode

}
}

//print a clock using words rather than numbers
void word_clock() {

cls();

char numbers[19][10] = {
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
"eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
char numberstens[5][7] = {
"ten", "twenty", "thirty", "forty", "fifty"
};

//potentially 3 lines to display
char str_a[8];
char str_b[8];
char str_c[8];

//byte hours_y, mins_y; //hours and mins and positions for hours and mins lines

byte hours = rtc[2];
if (hours > 12) {
hours = hours - ampm * 12;
}
if (hours < 1) {
hours = hours + ampm * 12;
}

get_time(); //get the time from the clock chip
byte old_mins = 100; //store mins in old_mins. We compare mins and old mins & when they are different we redraw the display. Set this to 100 initially so display is drawn when mode starts.
byte mins;

//run clock main loop as long as run_mode returns true
while (run_mode()) {

//check for button press
if (buttonA.uniquePress()) {
  switch_mode();
  return;
}
if (buttonB.uniquePress()) {
  display_date();
}

get_time(); //get the time from the clock chip
mins = rtc[1];  //get mins


//if mins is different from old_mins - redraw display
if (mins != old_mins) {

  //update old_mins with current mins value
  old_mins = mins;

  //reset these for comparison next time
  mins = rtc[1];
  hours = rtc[2];

  //make hours into 12 hour format
  if (hours > 12) {
    hours = hours - 12;
  }
  if (hours == 0) {
    hours = 12;
  }

  //split mins value up into two separate digits
  int minsdigit = rtc[1] % 10;
  byte minsdigitten = (rtc[1] / 10) % 10;

  //if mins <= 10 , then top line has to read "minsdigti past" and bottom line reads hours
  if (mins < 10) {
    strcpy (str_a, numbers[minsdigit - 1]);
    strcpy (str_b, "PAST");
    strcpy (str_c, numbers[hours - 1]);
  }

  //if mins = 10, cant use minsdigit as above, so soecial case to print 10 past /n hour.
  if (mins == 10) {
    strcpy (str_a, numbers[9]);
    strcpy (str_b, " PAST");
    strcpy (str_c, numbers[hours - 1]);
  }

  //if time is not on the hour - i.e. both mins digits are not zero,
  //then make first line read "hours" and 2 & 3rd lines read "minstens"  "mins" e.g. "three /n twenty /n one"
  else if (minsdigitten != 0 && minsdigit != 0  ) {

    strcpy (str_a, numbers[hours - 1]);

    //if mins is in the teens, use teens from the numbers array for the 2nd line, e.g. "fifteen"
    //if (mins >= 11 && mins <= 19) {
    if (mins <= 19) {
      strcpy (str_b, numbers[mins - 1]);
    }
    else {
      strcpy (str_b, numberstens[minsdigitten - 1]);

      strcpy (str_c, numbers[minsdigit - 1]);
    }
  }
  // if mins digit is zero, don't print it. read read "hours" "minstens" e.g. "three /n twenty"
  else if (minsdigitten != 0 && minsdigit == 0  ) {
    strcpy (str_a, numbers[hours - 1]);
    strcpy (str_b, numberstens[minsdigitten - 1]);
    strcpy (str_c, "");
  }

  //if both mins are zero, i.e. it is on the hour, the top line reads "hours" and bottom line reads "o'clock"
  else if (minsdigitten == 0 && minsdigit == 0  ) {
    strcpy (str_a, numbers[hours - 1]);
    strcpy (str_b, "O'CLOCK");
    strcpy (str_c, "");
  }

}//end worknig out time

//run in a loop
//print line a "twelve"
byte len = 0;
while (str_a[len]) {
  len++;
}; //get length of message
byte offset_top = (31 - ((len - 1) * 4)) / 2; //

//plot hours line
byte i = 0;
while (str_a[i]) {
  puttinychar((i * 4) + offset_top, 1, str_a[i]);
  i++;
}

//hold display but check for button presses
int counter = 1000;
while (counter > 0){
  //check for button press
  if (buttonA.uniquePress()) {
    switch_mode();
    return;
  }
  if (buttonB.uniquePress()) {
    display_date();
  }
delay(1);
counter--;
}
fade_down();

//print line b
len = 0;
while (str_b[len]) {
  len++;
}; //get length of message
offset_top = (31 - ((len - 1) * 4)) / 2; 

i = 0;
while (str_b[i]) {
  puttinychar((i * 4) + offset_top, 1, str_b[i]);
  i++;
}

//hold display but check for button presses
counter = 1000;
while (counter > 0){
  if (buttonA.uniquePress()) {
    switch_mode();
    return;
  }
  if (buttonB.uniquePress()) {
    display_date();
  }
  delay(1);
  counter--;
}
fade_down();

//print line c if there.
len = 0;
while (str_c[len]) {
  len++;
}; //get length of message
offset_top = (31 - ((len - 1) * 4)) / 2; 

i = 0;
while (str_c[i]) {
  puttinychar((i * 4) + offset_top, 1, str_c[i]);
  i++;
}
counter = 1000;
while (counter > 0){
  //check for button press
  if (buttonA.uniquePress()) {
    switch_mode();
    return;
  }
  if (buttonB.uniquePress()) {
    display_date();
  }  
  delay(1);
  counter--;
}
fade_down();


//hold display blank but check for button presses before starting again.
counter = 1000;
while (counter > 0){
   //check for button press
  if (buttonA.uniquePress()) {
    switch_mode();
    return;
  }
  if (buttonB.uniquePress()) {
    display_date();
  }  
  delay(1);
  counter--;
}
Enter fullscreen mode Exit fullscreen mode

}
fade_down();
}

/// scroll message - not used at present - too slow.
void scroll() {

char message[] = {"Hello There "};

cls();
byte p = 6; //current pos in string
byte chara[] = {0, 1, 2, 3, 4, 5}; //chars from string
int x[] = {0, 6, 12, 18, 24, 30}; //xpos for each char
byte y = 0; //y pos

// clear_buffer();

while (message[p] != '\0') {

//draw all 6 chars
for (byte c = 0; c < 6; c++) {

  putnormalchar(x[c],y,message[ chara[c] ]);


  //draw a line of pixels turned off after each char,otherwise the gaps between the chars have pixels left in them from the previous char
  for (byte yy = 0 ; yy < 8; yy ++) {
    plot(x[c] + 5, yy, 0);
  }

  //take one off each chars position
  x[c] = x[c] - 1;
}

//reset a char if it's gone off screen
for (byte i = 0; i <= 5; i++) {
  if (x[i] < -5 ) {
    x[i] = 31;
    chara[i] = p;
    p++;
  }
}
Enter fullscreen mode Exit fullscreen mode

}
}

//display_date - print the day of week, date and month with a flashing cursor effect
void display_date()
{

cls();
//read the date from the DS1307

byte dow = rtc[3]; // day of week 0 = Sunday
byte date = rtc[4];
byte month = rtc[5] - 1;

//array of month names to print on the display. Some are shortened as we only have 8 characters across to play with
char monthnames[12][9] = {
"January", "February", "March", "April", "May", "June", "July", "August", "Sept", "October", "November", "December"
};

//print the day name

//get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset
byte len = 0;
while(daysfull[dow][len]) {
len++;
};
byte offset = (31 - ((len-1)*4)) / 2; //our offset to centre up the text

//print the name

int i = 0;
while(daysfull[dow][i])
{
puttinychar((i*4) + offset , 1, daysfull[dow][i]);
i++;
}
delay(1000);
fade_down();
cls();

// print date numerals
char buffer[3];
itoa(date,buffer,10);
offset = 10; //offset to centre text if 3 chars - e.g. 3rd

// first work out date 2 letter suffix - eg st, nd, rd, th etc
// char suffix[4][3]={"st", "nd", "rd", "th" }; is defined at top of code
byte s = 3;
if(date == 1 || date == 21 || date == 31) {
s = 0;
}
else if (date == 2 || date == 22) {
s = 1;
}
else if (date == 3 || date == 23) {
s = 2;
}

//print the 1st date number
puttinychar(0+offset, 1, buffer[0]);

//if date is under 10 - then we only have 1 digit so set positions of sufix etc one character nearer
byte suffixposx = 4;

//if date over 9 then print second number and set xpos of suffix to be 1 char further away
if (date > 9){
suffixposx = 8;
puttinychar(4+offset, 1, buffer[1]);
offset = 8; //offset to centre text if 4 chars
}

//print the 2 suffix characters
puttinychar(suffixposx+offset, 1, suffix[s][0]);
puttinychar(suffixposx+4+offset, 1, suffix[s][1]);

delay(1000);
fade_down();

//print the month name

//get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset
len = 0;
while(monthnames[month][len]) {
len++;
};
offset = (31 - ((len-1)*4)) / 2; //our offset to centre up the text
i = 0;
while(monthnames[month][i])
{

puttinychar((i*4) +offset, 1, monthnames[month][i]);
i++;
}

delay(1000);
fade_down();
}

//dislpay menu to change the clock mode
void switch_mode() {

//remember mode we are in. We use this value if we go into settings mode, so we can change back from settings mode (6) to whatever mode we were in.
old_mode = clock_mode;

char* modes[] = {
"Basic", "Small", "Slide", "Words", "Setup"
};

byte next_clock_mode;
byte firstrun = 1;

//loop waiting for button (timeout after 35 loops to return to mode X)
for (int count = 0; count < 35 ; count++) {

//if user hits button, change the clock_mode
if (buttonA.uniquePress() || firstrun == 1) {

  count = 0;
  cls();

  if (firstrun == 0) {
    clock_mode++;
  }
  if (clock_mode > NUM_DISPLAY_MODES + 1 ) {
    clock_mode = 0;
  }

  //print arrown and current clock_mode name on line one and print next clock_mode name on line two
  char str_top[9];

  //strcpy (str_top, "-");
  strcpy (str_top, modes[clock_mode]);

  next_clock_mode = clock_mode + 1;
  if (next_clock_mode >  NUM_DISPLAY_MODES + 1 ) {
    next_clock_mode = 0;
  }

  byte i = 0;
  while (str_top[i]) {
    putnormalchar(i * 6, 0, str_top[i]);
    i++;
  }
  firstrun = 0;
}
delay(50);
Enter fullscreen mode Exit fullscreen mode

}
}

//run clock main loop as long as run_mode returns true
byte run_mode() {

//if random mode is on... check the hour when we change mode.
if (random_mode) {
//if hour value in change mode time = hours. then reurn false = i.e. exit mode.
if (change_mode_time == rtc[2]) {
//set the next random clock mode and time to change it
set_next_random();
//exit the current mode.
return 0;
}
}
//else return 1 - keep running in this mode
return 1;
}

//set the next hour the clock will change mode when random mode is on
void set_next_random() {

//set the next hour the clock mode will change - current time plus 1 - 4 hours
get_time();
change_mode_time = rtc[2] + random (1, 5);

//if change_mode_time now happens to be over 23, then set it to between 1 and 3am
if (change_mode_time > 23) {
change_mode_time = random (1, 4);
}

//set the new clock mode
clock_mode = random(0, NUM_DISPLAY_MODES + 1); //pick new random clock mode
}

//dislpay menu to change the clock settings
void setup_menu() {

char* set_modes[] = {
"Rndom", "24 Hr","Set", "Brght", "Exit"};
if (ampm == 0) {
set_modes[1] = ("12 Hr");
}

byte setting_mode = 0;
byte next_setting_mode;
byte firstrun = 1;

//loop waiting for button (timeout after 35 loops to return to mode X)
for(int count=0; count < 35 ; count++) {

//if user hits button, change the clock_mode
if(buttonA.uniquePress() || firstrun == 1){

  count = 0;
  cls();

  if (firstrun == 0) { 
    setting_mode++; 
  } 
  if (setting_mode > NUM_SETTINGS_MODES) { 
    setting_mode = 0; 
  }

  //print arrown and current clock_mode name on line one and print next clock_mode name on line two
  char str_top[9];

  strcpy (str_top, set_modes[setting_mode]);

  next_setting_mode = setting_mode + 1;
  if (next_setting_mode > NUM_SETTINGS_MODES) { 
    next_setting_mode = 0; 
  }

  byte i = 0;
  while(str_top[i]) {
    putnormalchar(i*6, 0, str_top[i]); 
    i++;
  }

  firstrun = 0;
}
delay(50); 
Enter fullscreen mode Exit fullscreen mode

}

//pick the mode
switch(setting_mode){
case 0:
set_random();
break;
case 1:
set_ampm();
break;
case 2:
set_time();
break;
case 3:
set_intensity();
break;
case 4:
//exit menu
break;
}

//change the clock from mode 6 (settings) back to the one it was in before
clock_mode=old_mode;
}

//toggle random mode - pick a different clock mode every few hours
void set_random(){
cls();

char text_a[9] = "Off";
char text_b[9] = "On";
byte i = 0;

//if random mode is on, turn it off
if (random_mode){

//turn random mode off
random_mode = 0;

//print a message on the display
while(text_a[i]) {
  putnormalchar((i*6), 0, text_a[i]);
  i++;
}
Enter fullscreen mode Exit fullscreen mode

} else {
//turn randome mode on.
random_mode = 1;

//set hour mode will change
set_next_random();

//print a message on the display
while(text_b[i]) {
  putnormalchar((i*6), 0, text_b[i]);
  i++;
}  
Enter fullscreen mode Exit fullscreen mode

}
delay(1500); //leave the message up for a second or so
}

//set 12 or 24 hour clock
void set_ampm() {

// AM/PM or 24 hour clock mode - flip the bit (makes 0 into 1, or 1 into 0 for ampm mode)
ampm = (ampm ^ 1);
cls();
}

//change screen intensityintensity
void set_intensity() {

cls();

byte i = 0;
char text[7] = "Bright";
while(text[i]) {
puttinychar((i*4)+4, 0, text[i]);
i++;
}

//wait for button input
while (!buttonA.uniquePress()) {

levelbar (0,6,(intensity*2)+2,2);    //display the intensity level as a bar
while (buttonB.isPressed()) {

  if(intensity == 15) { 
    intensity = 0;
    cls (); 
  } 
  else {
    intensity++; 
  }
  //print the new value 
  i = 0;
  while(text[i]) {
    puttinychar((i*4)+4, 0, text[i]);
    i++;
  }

  //display the intensity level as a bar
  levelbar (0,6,(intensity*2)+2,2);    

  //change the brightness setting on the displays
  for (byte address = 0; address < 4; address++) {
    lc.setIntensity(address, intensity);
  }
  delay(150);
}
Enter fullscreen mode Exit fullscreen mode

}
}

// display a horizontal bar on the screen at offset xposr by ypos with height and width of xbar, ybar
void levelbar (byte xpos, byte ypos, byte xbar, byte ybar) {
for (byte x = 0; x < xbar; x++) {
for (byte y = 0; y <= ybar; y++) {
plot(x+xpos, y+ypos, 1);
}
}
}

//set time and date routine
void set_time() {

cls();

//fill settings with current clock values read from clock
get_time();
byte set_min = rtc[1];
byte set_hr = rtc[2];
byte set_date = rtc[4];
byte set_mnth = rtc[5];
int set_yr = rtc[6];

//Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
set_date = set_value(2, set_date, 1, 31);
set_mnth = set_value(3, set_mnth, 1, 12);
set_yr = set_value(4, set_yr, 2013, 2099);
set_hr = set_value(1, set_hr, 0, 23);
set_min = set_value(0, set_min, 0, 59);

ds1307.adjust(DateTime(set_yr, set_mnth, set_date, set_hr, set_min));

cls();
}

//used to set min, hr, date, month, year values. pass
//message = which 'set' message to print,
//current value = current value of property we are setting
//reset_value = what to reset value to if to rolls over. E.g. mins roll from 60 to 0, months from 12 to 1
//rollover limit = when value rolls over
int set_value(byte message, int current_value, int reset_value, int rollover_limit){

cls();
char messages[6][17] = {
"Set Mins", "Set Hour", "Set Day", "Set Mnth", "Set Year"};

//Print "set xyz" top line
byte i = 0;
while(messages[message][i])
{
puttinychar(i*4 , 1, messages[message][i]);
i++;
}

delay(2000);
cls();

//print digits bottom line
char buffer[5] = " ";
itoa(current_value,buffer,10);
puttinychar(0 , 1, buffer[0]);
puttinychar(4 , 1, buffer[1]);
puttinychar(8 , 1, buffer[2]);
puttinychar(12, 1, buffer[3]);

delay(300);
//wait for button input
while (!buttonA.uniquePress()) {

while (buttonB.isPressed()){

  if(current_value < rollover_limit) { 
    current_value++;
  } 
  else {
    current_value = reset_value;
  }
  //print the new value
  itoa(current_value, buffer ,10);
  puttinychar(0 , 1, buffer[0]); 
  puttinychar(4 , 1, buffer[1]); 
  puttinychar(8 , 1, buffer[2]); 
  puttinychar(12, 1, buffer[3]);    
  delay(150);
}
Enter fullscreen mode Exit fullscreen mode

}
return current_value;
}

void get_time()
{
//get time
DateTime now = ds1307.now();
//save time to array
rtc[6] = now.year();
rtc[5] = now.month();
rtc[4] = now.day();
rtc[3] = now.dayOfTheWeek(); //returns 0-6 where 0 = Sunday
rtc[2] = now.hour();
rtc[1] = now.minute();
rtc[0] = now.second();

//flash arduino led on pin 13 every second
//if ( (rtc[0] % 2) == 0) {
// digitalWrite(13, HIGH);
//}
//else {
// digitalWrite(13, LOW);
//}

//print the time to the serial port - useful for debuging RTC issues
/*
Serial.print(rtc[2]);
Serial.print(":");
Serial.print(rtc[1]);
Serial.print(":");
Serial.println(rtc[0]);
*/
}

Top comments (0)