diff --git a/EPD/EPD_ble.c b/EPD/EPD_ble.c index 4f2fdd1..46ccaed 100644 --- a/EPD/EPD_ble.c +++ b/EPD/EPD_ble.c @@ -13,7 +13,10 @@ #include #include "nordic_common.h" #include "ble_srv_common.h" +#include "nrf_delay.h" +#include "nrf_gpio.h" #include "nrf_nvmc.h" +#include "nrf_soc.h" #include "nrf_log.h" #include "EPD_4in2.h" #include "EPD_4in2_V2.h" @@ -26,6 +29,7 @@ #define BLE_UUID_EPD_CHARACTERISTIC 0x0002 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define EPD_CONFIG_SIZE (sizeof(epd_config_t) / sizeof(uint8_t)) /** EPD drivers */ static epd_driver_t epd_drivers[] = { @@ -60,7 +64,7 @@ static void epd_config_load(epd_config_t *cfg) static void epd_config_save(epd_config_t *cfg) { nrf_nvmc_page_erase(BLE_EPD_CONFIG_ADDR); - nrf_nvmc_write_bytes(BLE_EPD_CONFIG_ADDR, (uint8_t*)cfg, sizeof(epd_config_t) / sizeof(uint8_t)); + nrf_nvmc_write_bytes(BLE_EPD_CONFIG_ADDR, (uint8_t*)cfg, EPD_CONFIG_SIZE); } /**@brief Function for handling the @ref BLE_GAP_EVT_CONNECTED event from the S110 SoftDevice. @@ -155,6 +159,21 @@ static void epd_service_process(ble_epd_t * p_epd, uint8_t * p_data, uint16_t le DEV_Delay_ms(200); break; + case EPD_CMD_SET_CONFIG: + if (length < 2) return; + memcpy(&p_epd->config, &p_data[1], (length - 1 > EPD_CONFIG_SIZE) ? EPD_CONFIG_SIZE : length - 1); + epd_config_save(&p_epd->config); + break; + + case EPD_CMD_SYS_RESET: + NVIC_SystemReset(); + break; + + case EPD_CMD_SYS_SLEEP: + ble_epd_sleep_prepare(p_epd); + sd_power_system_off(); + break; + default: break; } @@ -206,10 +225,12 @@ void ble_epd_on_ble_evt(ble_epd_t * p_epd, ble_evt_t * p_ble_evt) switch (p_ble_evt->header.evt_id) { case BLE_GAP_EVT_CONNECTED: + nrf_gpio_pin_toggle(p_epd->config.led_pin); on_connect(p_epd, p_ble_evt); break; case BLE_GAP_EVT_DISCONNECTED: + nrf_gpio_pin_toggle(p_epd->config.led_pin); on_disconnect(p_epd, p_ble_evt); break; @@ -285,19 +306,8 @@ static uint32_t epd_service_init(ble_epd_t * p_epd) return err_code; } -uint32_t ble_epd_init(ble_epd_t * p_epd) +static void epd_config_init(ble_epd_t * p_epd) { - if (p_epd == NULL) - { - return NRF_ERROR_NULL; - } - - // Initialize the service structure. - p_epd->conn_handle = BLE_CONN_HANDLE_INVALID; - p_epd->is_notification_enabled = false; - - // Load epd config - epd_config_load(&p_epd->config); bool save_config = false; if (p_epd->config.mosi_pin == 0xFF && p_epd->config.sclk_pin == 0xFF && p_epd->config.cs_pin == 0xFF && p_epd->config.dc_pin == 0xFF && @@ -330,9 +340,40 @@ uint32_t ble_epd_init(ble_epd_t * p_epd) p_epd->config.driver_id = p_epd->driver->id; save_config = true; } - if (save_config) { + if (save_config) + { epd_config_save(&p_epd->config); } +} + +void ble_epd_sleep_prepare(ble_epd_t * p_epd) +{ + // Turn off led + nrf_gpio_pin_set(p_epd->config.led_pin); + // Prepare wakeup pin + nrf_gpio_cfg_sense_input(p_epd->config.wakeup_pin, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); +} + +uint32_t ble_epd_init(ble_epd_t * p_epd) +{ + if (p_epd == NULL) + { + return NRF_ERROR_NULL; + } + + // Initialize the service structure. + p_epd->conn_handle = BLE_CONN_HANDLE_INVALID; + p_epd->is_notification_enabled = false; + + // Load epd config + epd_config_load(&p_epd->config); + epd_config_init(p_epd); + + // Init led pin + nrf_gpio_cfg_output(p_epd->config.led_pin); + nrf_gpio_pin_clear(p_epd->config.led_pin); + nrf_delay_ms(50); + nrf_gpio_pin_set(p_epd->config.led_pin); // Add the service. return epd_service_init(p_epd); diff --git a/EPD/EPD_ble.h b/EPD/EPD_ble.h index ecf7f5f..5a97a08 100644 --- a/EPD/EPD_ble.h +++ b/EPD/EPD_ble.h @@ -34,18 +34,24 @@ typedef struct uint8_t busy_pin; uint8_t bs_pin; uint8_t driver_id; + uint8_t wakeup_pin; + uint8_t led_pin; } epd_config_t; /**< EPD Service command IDs. */ enum EPD_CMDS { - EPD_CMD_SET_PINS, - EPD_CMD_INIT, - EPD_CMD_CLEAR, - EPD_CMD_SEND_COMMAND, - EPD_CMD_SEND_DATA, - EPD_CMD_DISPLAY, - EPD_CMD_SLEEP, + EPD_CMD_SET_PINS, /**< set EPD pin mapping. */ + EPD_CMD_INIT, /**< init EPD display driver */ + EPD_CMD_CLEAR, /**< clear EPD screen */ + EPD_CMD_SEND_COMMAND, /**< send command to EPD */ + EPD_CMD_SEND_DATA, /**< send data to EPD */ + EPD_CMD_DISPLAY, /**< diaplay EPD ram on screen */ + EPD_CMD_SLEEP, /**< EPD enter sleep mode */ + + EPD_CMD_SET_CONFIG = 0x90, /**< set full EPD config */ + EPD_CMD_SYS_RESET = 0x91, /**< MCU reset */ + EPD_CMD_SYS_SLEEP = 0x92, /**< MCU enter sleep mode */ }; /**< EPD driver IDs. */ @@ -86,6 +92,12 @@ typedef struct epd_config_t config; /**< EPD config */ } ble_epd_t; +/**@brief Function for preparing sleep mode. + * + * @param[in] p_epd EPD Service structure. + */ +void ble_epd_sleep_prepare(ble_epd_t * p_epd); + /**@brief Function for initializing the EPD Service. * * @param[out] p_epd EPD Service structure. This structure must be supplied diff --git a/README.md b/README.md index 1cfc4ea..4976ed7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 4.2 寸电子墨水屏固件,带有一个[网页版上位机](https://tsl0922.github.io/EPD-nRF51/),可以通过蓝牙传输图像到墨水屏。 -理论上支持所有 nRF51 系列 MCU,内置 3 个微雪 4.2 寸墨水屏驱动(可切换),同时还支持自定义墨水屏到 MCU 的引脚映射。 +理论上支持所有 nRF51 系列 MCU,内置 3 个微雪 4.2 寸墨水屏驱动(可切换),同时还支持自定义墨水屏到 MCU 的引脚映射,支持睡眠唤醒(NFC / 无线充电器)。 ## 支持设备 @@ -14,7 +14,8 @@ ROM:128K 驱动:EPD_4in2 - 引脚映射:0508090A0B0C0D + 屏幕引脚:0508090A0B0C0D + 线圈引脚:07 ``` ![](html/images/1.jpg) @@ -27,9 +28,13 @@ ROM:256K 驱动:EPD_4in2b_V2 - 引脚映射:0A0B0C0D0E0F10 + 屏幕引脚:0A0B0C0D0E0F10 + 线圈引脚:09 + LED 引脚:05 ``` + ![](html/images/2.jpg) + 默认驱动和引脚映射为黑白双色版本,其它版本需要切换驱动并修改引脚映射。 ## 上位机 diff --git a/html/images/1.jpg b/html/images/1.jpg index 1c4c4f1..210a07c 100644 Binary files a/html/images/1.jpg and b/html/images/1.jpg differ diff --git a/html/images/2.jpg b/html/images/2.jpg new file mode 100644 index 0000000..a1da34f Binary files /dev/null and b/html/images/2.jpg differ diff --git a/html/index.html b/html/index.html index 7203195..27fbb74 100644 --- a/html/index.html +++ b/html/index.html @@ -21,7 +21,6 @@

4.2 寸电子墨水屏蓝牙控制器(nRF51)

-
蓝牙
@@ -90,21 +89,36 @@

4.2 寸电子墨水屏蓝牙控制器(nRF51)

  • 指令列表(指令和参数全部要使用十六进制):
      -
    • 00+引脚配置: 设置引脚映射(见上面引脚配置)
    • -
    • 01+驱动 ID: 驱动初始化(支持的驱动 ID: 01/02/03
    • -
    • 02: 清空屏幕(把屏幕刷为白色)
    • -
    • 03+命令: 发送命令到屏幕(请参考屏幕主控手册)
    • -
    • 04+数据: 写入数据到屏幕内存(同上)
    • -
    • 05: 刷新屏幕(显示已写入屏幕内存的数据)
    • -
    • 06: 让屏幕进入睡眠状态
    • +
    • 驱动相关: +
        +
      • 00+引脚配置: 设置引脚映射(见上面引脚配置)
      • +
      • 01+驱动 ID: 驱动初始化(支持的驱动 ID: 01/02/03
      • +
      • 02: 清空屏幕(把屏幕刷为白色)
      • +
      • 03+命令: 发送命令到屏幕(请参考屏幕主控手册)
      • +
      • 04+数据: 写入数据到屏幕内存(同上)
      • +
      • 05: 刷新屏幕(显示已写入屏幕内存的数据)
      • +
      • 06: 屏幕睡眠
      • +
      +
    • +
    • 系统相关: +
        +
      • 90+配置: 写入配置信息(重启生效,格式参考源码 epd_config_t
      • +
      • 91: 系统重启
      • +
      • 92: 系统睡眠
      • +
      +
  • +

    + 系统睡眠后可通过线圈(NFC/无线充电器)唤醒(需正确配置线圈对应的引脚才有效),否则一旦系统睡眠只有重新上电才能开启蓝牙。如果价签上带有 LED,系统启动时 LED 会闪一下(需正确配置 LED 对应的引脚才有效),以便知道系统是否已经被线圈唤醒。 +

    致谢:屏幕驱动代码来自微雪 E-Paper Shield,本网页代码最初基于 atc1441/ATC_TLSR_Paper 项目的网页控制端代码修改而来。

    + diff --git a/html/js/main.js b/html/js/main.js index 2f396ef..675eff3 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -39,24 +39,20 @@ async function sendCommand(cmd) { } async function sendcmd(cmdTXT) { - let cmd = hexToBytes(cmdTXT); addLog(`发送命令: ${cmdTXT}`); - await sendCommand(cmd); + await sendCommand(hexToBytes(cmdTXT)); } async function setDriver() { - let epdDriver = document.getElementById("epddriver").value; - let pins = document.getElementById("epdpins").value; + const epdDriver = document.getElementById("epddriver").value; + const pins = document.getElementById("epdpins").value; await sendcmd("00" + pins); await sendcmd("01" + epdDriver); - await sendcmd("06"); } async function clearscreen() { if(confirm('确认清除屏幕内容?')) { - await sendcmd("01"); await sendcmd("02"); - await sendcmd("06"); } } @@ -76,11 +72,10 @@ async function sendIMGArray(imgArray, type = 'bw'){ async function sendimg(cmdIMG) { startTime = new Date().getTime(); - let epdDriver = document.getElementById("epddriver").value; - let imgArray = cmdIMG.replace(/(?:\r\n|\r|\n|,|0x| )/g, ''); + const epdDriver = document.getElementById("epddriver").value; + const imgArray = cmdIMG.replace(/(?:\r\n|\r|\n|,|0x| )/g, ''); const bwArrLen = (canvas.width/8) * canvas.height * 2; - await sendcmd("01"); if (imgArray.length == bwArrLen * 2) { await sendcmd("0310"); await sendIMGArray(imgArray.slice(0, bwArrLen - 1)); @@ -92,16 +87,14 @@ async function sendimg(cmdIMG) { } await sendcmd("05"); - let sendTime = (new Date().getTime() - startTime) / 1000.0; + const sendTime = (new Date().getTime() - startTime) / 1000.0; addLog(`发送完成!耗时: ${sendTime}s`); setStatus(`发送完成!耗时: ${sendTime}s`); - - await sendcmd("06"); } function updateButtonStatus() { - let connected = gattServer != null && gattServer.connected; - let status = connected ? null : 'disabled'; + const connected = gattServer != null && gattServer.connected; + const status = connected ? null : 'disabled'; document.getElementById("sendcmdbutton").disabled = status; document.getElementById("clearscreenbutton").disabled = status; document.getElementById("sendimgbutton").disabled = status; @@ -117,8 +110,10 @@ function disconnect() { async function preConnect() { if (gattServer != null && gattServer.connected) { - if (bleDevice != null && bleDevice.gatt.connected) + if (bleDevice != null && bleDevice.gatt.connected) { + await sendcmd("06"); bleDevice.gatt.disconnect(); + } } else { connectTrys = 0; @@ -159,11 +154,13 @@ async function connect() { await epdCharacteristic.startNotifications(); epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => { - addLog(`> 收到配置:${bytesToHex(event.target.value.buffer.slice(0,8))}`); + addLog(`> 收到配置:${bytesToHex(event.target.value.buffer)}`); document.getElementById("epdpins").value = bytesToHex(event.target.value.buffer.slice(0, 7)); document.getElementById("epddriver").value = bytesToHex(event.target.value.buffer.slice(7, 8)); }); + await sendcmd("01"); + document.getElementById("connectbutton").innerHTML = '断开'; updateButtonStatus(); } @@ -174,8 +171,8 @@ function setStatus(statusText) { } function addLog(logTXT) { - var today = new Date(); - var time = ("0" + today.getHours()).slice(-2) + ":" + ("0" + today.getMinutes()).slice(-2) + ":" + ("0" + today.getSeconds()).slice(-2) + " : "; + const today = new Date(); + const time = ("0" + today.getHours()).slice(-2) + ":" + ("0" + today.getMinutes()).slice(-2) + ":" + ("0" + today.getSeconds()).slice(-2) + " : "; document.getElementById("log").innerHTML += time + logTXT + '
    '; console.log(time + logTXT); while ((document.getElementById("log").innerHTML.match(/
    /g) || []).length > 10) { @@ -198,15 +195,21 @@ function bytesToHex(data) { } function intToHex(intIn) { - var stringOut = ""; - stringOut = ("0000" + intIn.toString(16)).substr(-4) + let stringOut = ("0000" + intIn.toString(16)).substr(-4) return stringOut.substring(2, 4) + stringOut.substring(0, 2); } function updateImageData(canvas) { + const epdDriver = document.getElementById("epddriver").value; + const dithering = document.getElementById('dithering').value; document.getElementById('cmdIMAGE').value = bytesToHex(canvas2bytes(canvas, 'bw')); - if (document.getElementById('dithering').value.startsWith('bwr')) { - document.getElementById('cmdIMAGE').value += bytesToHex(canvas2bytes(canvas, 'bwr')); + if (epdDriver === '03') { + if (dithering.startsWith('bwr')) { + document.getElementById('cmdIMAGE').value += bytesToHex(canvas2bytes(canvas, 'bwr')); + } else { + const count = document.getElementById('cmdIMAGE').value.length; + document.getElementById('cmdIMAGE').value += 'F'.repeat(count); + } } } diff --git a/main.c b/main.c index c159c01..0fb2aa0 100644 --- a/main.c +++ b/main.c @@ -187,6 +187,9 @@ static void conn_params_init(void) */ static void sleep_mode_enter(void) { + // Prepare wakeup pin + ble_epd_sleep_prepare(&m_epd); + // Go to system-off mode (this function will not return; wakeup will cause a reset). uint32_t err_code = sd_power_system_off(); APP_ERROR_CHECK(err_code);