text: /**
   * Copyright (c) 2017 - 2018, Nordic Semiconductor ASA
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification,
   * are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form, except as embedded into a Nordic
   *    Semiconductor ASA integrated circuit in a product or a software update for
   *    such product, must reproduce the above copyright notice, this list of
   *    conditions and the following disclaimer in the documentation and/or other
   *    materials provided with the distribution.
   *
   * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
   *    contributors may be used to endorse or promote products derived from this
   *    software without specific prior written permission.
   *
   * 4. This software, with or without modification, must only be used with a
   *    Nordic Semiconductor ASA integrated circuit.
   *
   * 5. Any software provided in binary form under this license must not be reverse
   *    engineered, decompiled, modified and/or disassembled.
   *
   * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
   * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   */
  
  #include "st7565LCD.h"
  #include "app_util_platform.h"
  #include "nrf_delay.h"
  #include "nrf_gpio.h"
  #include "nrf_spi_mngr.h"
  #include "boards.h"
  #include "nordic_common.h"
  
  #define ST7565_PIN_A0      ARDUINO_7_PIN
  #define ST7565_PIN_RESET   ARDUINO_12_PIN
  
  #define ST7565_CMD_SET_COLUMN_UPPER ((uint8_t)0x10)
  #define ST7565_CMD_SET_COLUMN_LOWER ((uint8_t)0x00)
  #define ST7565_CMD_RMW              ((uint8_t)0xE0)
  #define ST7565_CMD_SELECT_PAGE      ((uint8_t)0xB0)
  #define ST7565_CMD_VREG_2           ((uint8_t)0x22)
  #define ST7565_CMD_POWER_ON         ((uint8_t)0x2F)
  #define ST7565_CMD_START_LINE       ((uint8_t)0x40)
  #define ST7565_CMD_CONTRAST_SET     ((uint8_t)0x81)
  #define ST7565_CMD_SEGMENT_NORMAL   ((uint8_t)0xA0)
  #define ST7565_CMD_BIAS1            ((uint8_t)0xA2)
  #define ST7565_CMD_ST7565_NORMAL    ((uint8_t)0xA6)
  #define ST7565_CMD_ST7565_REVERSE   ((uint8_t)0xA7)
  #define ST7565_CMD_OFF              ((uint8_t)0xAE)
  #define ST7565_CMD_ON               ((uint8_t)0xAF)
  #define ST7565_CMD_COMMON_NORMAL    ((uint8_t)0xC0)
  #define ST7565_CMD_RESET            ((uint8_t)0xE2)
  #define ST7565_CONTRAST_VALUE       ((uint8_t)0x17)
  
  #define ST7565_PAGE_PIXELS_HEIGHT   ((uint8_t)8)
  #define ST7565_DISPLAY_CMD_LENGTH   ((uint8_t)5)
  #define ST7565_NUMBER_OF_PAGES      (ST7565_LCD_HEIGHT / ST7565_PAGE_PIXELS_HEIGHT)
  
  #define ST7565_QUEUE_LENGTH     15
  #define ST7565_SPI_INSTANCE_ID  0
  
  NRF_SPI_MNGR_DEF(m_nrf_spi_mngr, ST7565_QUEUE_LENGTH, ST7565_SPI_INSTANCE_ID);
  
  // Buffer for monochromatic display based on ST7565 driver.
  // Each bit of a buffer corresponds to different pixel on a screen.
  static uint8_t m_display_buffer[ST7565_LCD_WIDTH * ST7565_NUMBER_OF_PAGES];
  
  static const uint8_t m_column_lookup[] = {7, 6, 5, 4, 3, 2, 1, 0};
  
  // Buffer with commands used to display m_display_buffer on a screen.
  static NRF_SPI_MNGR_BUFFER_LOC_IND uint8_t m_command_buffer[] =
  {
      ST7565_CMD_SELECT_PAGE | 3,     // top page of the the screen
      ST7565_CMD_START_LINE,          // start with y = 0
      ST7565_CMD_SET_COLUMN_LOWER,    // start with x = 0
      ST7565_CMD_SET_COLUMN_UPPER,
      ST7565_CMD_RMW,                 // increment column addr at write
  
      ST7565_CMD_SELECT_PAGE | 2,
      ST7565_CMD_START_LINE,
      ST7565_CMD_SET_COLUMN_LOWER,
      ST7565_CMD_SET_COLUMN_UPPER,
      ST7565_CMD_RMW,
  
      ST7565_CMD_SELECT_PAGE | 1,
      ST7565_CMD_START_LINE,
      ST7565_CMD_SET_COLUMN_LOWER,
      ST7565_CMD_SET_COLUMN_UPPER,
      ST7565_CMD_RMW,
  
      ST7565_CMD_SELECT_PAGE,         // bottom page of the screen
      ST7565_CMD_START_LINE,
      ST7565_CMD_SET_COLUMN_LOWER,
      ST7565_CMD_SET_COLUMN_UPPER,
      ST7565_CMD_RMW,
  };
  
  // Initialization commands for ST7565 driver.
  static NRF_SPI_MNGR_BUFFER_LOC_IND uint8_t m_initialization[] =
  {
      ST7565_CMD_RESET,
      ST7565_CMD_OFF,
      ST7565_CMD_BIAS1,
      ST7565_CMD_SEGMENT_NORMAL,
      ST7565_CMD_COMMON_NORMAL,
      ST7565_CMD_VREG_2,
      ST7565_CMD_POWER_ON,
      ST7565_CMD_START_LINE,
      ST7565_CMD_ON,
      ST7565_CMD_CONTRAST_SET,
      ST7565_CONTRAST_VALUE,
      ST7565_CMD_ST7565_NORMAL
  };
  
  
  // Function called before SPI command is send to LCD display. I is used to drive A0 pin low.
  static void st7565_set_command_cb(void * p_user_data)
  {
      nrf_gpio_pin_clear(ST7565_PIN_A0);
  }
  
  
  // Function called before SPI data is send to LCD display. It is used to drive A0 pin high.
  static void st7565_set_data_cb(void * p_user_data)
  {
      nrf_gpio_pin_set(ST7565_PIN_A0);
  }
  
  
  // Function for displaying one of four pages on the LCD screen from m_display_buffer
  static ret_code_t display_page(uint16_t page)
  {
      ret_code_t ret_val;
  
      if (page >= ST7565_NUMBER_OF_PAGES)
      {
          return NRF_ERROR_INVALID_PARAM;
      }
  
      // Below structures have to be "static" - they cannot be placed on stack
      // since the transaction is scheduled and these structures most likely
      // will be referred after this function returns
      static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND transfer_cmd[] =
      {
          NRF_SPI_MNGR_TRANSFER(&m_command_buffer[ST7565_DISPLAY_CMD_LENGTH * 0], // page 0
                                ST7565_DISPLAY_CMD_LENGTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_command_buffer[ST7565_DISPLAY_CMD_LENGTH * 1], // page 1
                                ST7565_DISPLAY_CMD_LENGTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_command_buffer[ST7565_DISPLAY_CMD_LENGTH * 2], // page 2
                                ST7565_DISPLAY_CMD_LENGTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_command_buffer[ST7565_DISPLAY_CMD_LENGTH * 3], // page 3
                                ST7565_DISPLAY_CMD_LENGTH, NULL, 0)
      };
      static nrf_spi_mngr_transaction_t const transaction_cmd[] =
      {
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[0],    // page 0
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[1],    // page 1
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[2],    // page 2
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[3],    // page 3
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
      };
      static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND transfer_data[] =
      {
          NRF_SPI_MNGR_TRANSFER(&m_display_buffer[ST7565_LCD_WIDTH * 0], ST7565_LCD_WIDTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_display_buffer[ST7565_LCD_WIDTH * 1], ST7565_LCD_WIDTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_display_buffer[ST7565_LCD_WIDTH * 2], ST7565_LCD_WIDTH, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&m_display_buffer[ST7565_LCD_WIDTH * 3], ST7565_LCD_WIDTH, NULL, 0)
      };
      static nrf_spi_mngr_transaction_t const transaction_data[] =
      {
          {
              .begin_callback      = st7565_set_data_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_data[0],   // page 0
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_data_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_data[1],   // page 1
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_data_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_data[2],  // page 2
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_data_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_data[3],  // page 3
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
      };
      ret_val = nrf_spi_mngr_schedule(&m_nrf_spi_mngr, &transaction_cmd[page]);
      if (ret_val == NRF_SUCCESS)
      {
          ret_val = nrf_spi_mngr_schedule(&m_nrf_spi_mngr, &transaction_data[page]);
      }
  
      return ret_val;
  }
  
  
  static ret_code_t init_spi0_master(void)
  {
      // SPI0 (with transaction manager) initialization.
      nrf_drv_spi_config_t const m_master0_config =
      {
          .sck_pin        = ARDUINO_13_PIN,
          .mosi_pin       = ARDUINO_11_PIN,
          .miso_pin       = NRF_DRV_SPI_PIN_NOT_USED,
          .ss_pin         = ARDUINO_10_PIN,
          .irq_priority   = APP_IRQ_PRIORITY_LOWEST,
          .orc            = 0xFF,
          .frequency      = NRF_DRV_SPI_FREQ_8M,
          .mode           = NRF_DRV_SPI_MODE_0,
          .bit_order      = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST
      };
      return nrf_spi_mngr_init(&m_nrf_spi_mngr, &m_master0_config);
  }
  
  
  void st7565_clear_display(void)
  {
      memset((void *)m_display_buffer, 0, sizeof(m_display_buffer));
      st7565_display_screen();
  }
  
  
  ret_code_t st7565_init(void)
  {
      ret_code_t err_code;
      err_code = init_spi0_master();
  
      if (err_code == NRF_SUCCESS)
      {
          memset((void *)m_display_buffer, 0, sizeof(m_display_buffer));
  
          nrf_gpio_pin_dir_set(ST7565_PIN_A0, NRF_GPIO_PIN_DIR_OUTPUT);
          nrf_gpio_pin_dir_set(ST7565_PIN_RESET, NRF_GPIO_PIN_DIR_OUTPUT);
  
          nrf_gpio_pin_clear(ST7565_PIN_RESET);
          nrf_delay_ms(15);
          nrf_gpio_pin_set(ST7565_PIN_RESET);
          nrf_delay_ms(2);
  
          nrf_spi_mngr_transfer_t const transfers[] =
          {
              NRF_SPI_MNGR_TRANSFER(m_initialization, sizeof(m_initialization), NULL, 0)
          };
          err_code = nrf_spi_mngr_perform(&m_nrf_spi_mngr, NULL, transfers, ARRAY_SIZE(transfers), NULL);
      }
  
      return err_code;
  }
  
  
  void st7565_uninit(void)
  {
      nrf_spi_mngr_uninit(&m_nrf_spi_mngr);
  }
  
  
  void st7565_pixel_draw(uint16_t x, uint16_t y, uint32_t color)
  {
      ASSERT(x < ST7565_LCD_WIDTH);
      ASSERT(y < ST7565_LCD_HEIGHT);
  
      uint8_t page;
      uint8_t y_disp;
      uint8_t * p_byte;
  
      page = (uint8_t)(y >> 3);
      y_disp = m_column_lookup[y % 8];
      p_byte = &m_display_buffer[(ST7565_LCD_WIDTH - 1) * page + x + page];
  
      // In this example monochromatic LCD is used. Color > 0 sets pixel and color == 0 clears it.
      if (color != 0)
      {
          SET_BIT(*p_byte, y_disp);
      }
      else
      {
          CLR_BIT(*p_byte, y_disp);
      }
  }
  
  
  void st7565_rect_draw(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint32_t color)
  {
      if ((x + width) >= ST7565_LCD_WIDTH)
      {
          return;
      }
      if ((y + height) >= ST7565_LCD_HEIGHT)
      {
          return;
      }
  
      for (uint8_t idx_y = y; idx_y < (y + height); idx_y++)
      {
          for (uint8_t idx_x = x; idx_x < (x + width); idx_x++)
          {
              st7565_pixel_draw(idx_x, idx_y, color);
          }
      }
  }
  
  
  void st7565_display_screen(void)
  {
      for (uint8_t i = 0; i < ST7565_NUMBER_OF_PAGES; i++)
      {
          APP_ERROR_CHECK(display_page(i));
      }
  }
  
  
  void st7565_display_invert(bool invert)
  {
      static uint8_t NRF_SPI_MNGR_BUFFER_LOC_IND command[] = {
                  ST7565_CMD_ST7565_NORMAL, ST7565_CMD_ST7565_REVERSE
              };
  
      static nrf_spi_mngr_transfer_t const transfer_cmd[] =
      {
          NRF_SPI_MNGR_TRANSFER(&command[0], 1, NULL, 0),
          NRF_SPI_MNGR_TRANSFER(&command[1], 1, NULL, 0),
      };
      static nrf_spi_mngr_transaction_t const transaction_cmd[] =
      {
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[0],
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          },
          {
              .begin_callback      = st7565_set_command_cb,
              .end_callback        = NULL,
              .p_user_data         = NULL,
              .p_transfers         = &transfer_cmd[1],
              .number_of_transfers = 1,
              .p_required_spi_cfg  = NULL
          }
      };
      APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_spi_mngr, &transaction_cmd[invert]));
  }
  
  