feat: support 8gray. (#2)

This commit is contained in:
susabolca
2024-04-25 01:32:40 +08:00
committed by GitHub
parent 935e9d3431
commit 6b44d2b031
28 changed files with 1251 additions and 190 deletions

BIN
doc/pic_gray8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -1,15 +1,24 @@
# cc2640r2-etag
cc2640r2 电子标签改电子时钟固件.
> 显示时钟日历,或静态图片。
目前支持,
* cc2640r2l_2in13_ssd1680_250x122
* cc2640r2l_2in9_ssd1680a_296x128
* cc2640r2l_2in9_ssd1680a_296x128 **主要支持**
![cc2640r2l_2in9_ssd1680a_296x128](doc/pic1.jpg)
单节或者双节 CR2450 电池供电.
### 8级灰度支持
通过控制 LUT仅保留 Black 和 Red 的刷新,控制刷新电压及时间,在多次刷新下可获得灰度图片。
![8级灰度](doc/pic_gray8.jpg)
tools 下提供 bwr_gray8.act 调色板,便于在 Photoshop 里生成 BWR 色彩的8级灰度图像。
## 编译
IAR 9.40 或 CCS 12.5
@@ -22,7 +31,9 @@ SDK: simplelink_cc2640r2_sdk_1_40_00_45
电子标签为 cjtag 2 wire, 需要 xds 或者 jlink 烧写器.
#### cc2640r2l_2in9_ssd1680a_296x128
> 可自制 XDS110 烧写器
### cc2640r2l_2in9_ssd1680a_296x128
![cjtag](doc/pic2.jpg)
@@ -34,9 +45,15 @@ SDK: simplelink_cc2640r2_sdk_1_40_00_45
|4|TMS|
|7|NRST|
### 保留 SNV 数据
固件配置 SNV (非易失存储器) 为单页模式,数据保存在 FLash 中第 30 页中。
在首次烧录后,如需保留 LUT 等 SNV 设置,请在烧写器 (如 SmartRF Flash Programmer 2) 跳过第 30 页即可,设置即可保留。
## 低功耗蓝牙 BLE5
电子标签使用 BLE5 配置, 兼容 4.2.
电子标签使用 BLE5 进行配置。
可使用 tools 下 cc2640r2_etag.html (chrome 蓝牙) 配置, 或手机端 nRF connect App 配置.
@@ -48,3 +65,33 @@ GATT 配置,
- UUID: FFF2, 时区偏移分钟, int32, 默认为北京时区 (+8*60)
- UUID: FFF3, 电池电压 mV, uint16
- UUID: FFF4, 温度 摄氏度, int8, (-127 ~ +128)
- UUID: FFF5, RTC 微调, int8, (-5 ~ +5)
- UUID: FFFE, RxTx 服务, 图片模式、灰度刷新、内置LUT更新等
### Advertising
本固件通过 BLE Adv 中的 Service Data 通告当前 etag 的数据,格式如下
```
UUID, MAC address, Display Mode, Unix Epoch Time, Temperature, Battery Level.
```
通告间隔为 1s。
可通过 ble5_ctrl.py 批量获得近距离 etag 的信息。
## Webtools
当前配置能力由 cc2640r2_etag.html 完成,需使用支持 BLE5 蓝牙的 Chrome/Edge 浏览器打开。
时钟模式功能,
1. 获取当前 etag 的信息
2. 一键设置当前时间、当前时区、RTC 微调等
图片模式,
1. 选择 BWR 三色图像 (默认刷新15秒左右)
2. 选择 BWR 灰度图像 (8级灰度刷新2分钟左右)
SNV 功能,
1. 保存当前设置 (时区、RTC 微调,模式等)
2. 保存和查看自定义 LUT (可定义 3 个LUT)

View File

@@ -15,6 +15,7 @@
#include <time.h> // time
#include <stdint.h> // uint8_t
#include <string.h> // memset
// OBD
#include "OneBitDisplay.h"
@@ -27,6 +28,7 @@ OBDISP obd;
extern const uint8_t ucMirror[];
#if 0
/*
* <int.frac> format size (3.8) bits.
* int for 0-3 voltage
@@ -35,6 +37,7 @@ extern const uint8_t ucMirror[];
#define INTFRAC_V(x) (x>>8)
#define INTFRAC_mV(x) ((x&0xff)*1000/256)
#define INTFRAC2MV(x) (INTFRAC_mV(x)+(INTFRAC_V(x)*1000))
#endif
// https://www.mdpi.com/2072-666X/12/5/578
#define _VS(x) x<<6
@@ -43,6 +46,9 @@ extern const uint8_t ucMirror[];
#define VSL _VS(0b10)
#define VSH2 _VS(0b11)
#if 0
/* the LUT lots of values are zeros, use a lite version instead.
*/
static const uint8_t lut_full_bwr[] = {
// 0: LUTC x 7
// RP A B C D SRAB SRCD
@@ -55,7 +61,7 @@ static const uint8_t lut_full_bwr[] = {
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
// 56: LUTR x 7
0x1, VSL|0x2f, 0x0, VSH2|0x3f, 0x0, 0x1, 0x0,
0x1, VSL|0x2f, 0x0, VSH2|0x3f, 0x0, 0x1, 0xa,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
@@ -117,6 +123,124 @@ static void EPD_2IN9_Lut(const unsigned char *lut)
EPD_SSD_SendCommand(0x2C);
EPD_SSD_SendData(*(lut+232));
}
#endif
/* LUT lite is small than LUT used in SSD1680A, only 35 bytes.
* lut_lite_fast_bw is default lut for clock display,
* lut_lite_gray8_bwr is default lut for BLE display.
*/
#define LUT_LITE_LEN 35
// LUT for clock fast display. (only black/white)
static const uint8_t lut_lite_fast_bw[LUT_LITE_LEN] = {
// RP A B C D SRAB SRCD
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // LUTC
0x1, VSL|0x2f, 0x0, VSH2|0x3f, 0x0, 0x1, 0x0, // LUTR
0x1, VSL|0x3f, 0x0, 0x0, 0x0, 0x1, 0x0, // LUTW
0x1, VSH1|0x2f, 0x0, 0x0, 0x0, 0x1, 0x0, // LUTB
// FR
0x04, // 2: 50hz, 3: 75Hz, 4: 100Hz, 5: 125Hz
// EOPT VGH VSH1 VSH2 VSL VCOM
// 3F 03 04 2C
// 22 -20v 15v 3v -15v
0x22, 0x17, 0x41, 0x94, 0x32, 0x36
};
// LUT for BLE Gray display (8 steps)
static const uint8_t lut_lite_gray8_bwr[LUT_LITE_LEN] = {
// RP A B C D SRAB SRCD
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // LUTC
0x1, VSH2|0x3f, 0x0, 0x0, 0x0, 0x1, 0x0, // LUTR
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // LUTW
0x1, VSH1|0x03, 0x0, 0x0, 0x0, 0x1, 0x0, // LUTB
// FR
0x04, // 2: 50hz, 3: 75Hz, 4: 100Hz, 5: 125Hz
// EOPT VGH VSH1 VSH2 VSL VCOM
// 3F 03 04 2C
// 22 -20v 15v 3.0v -15v
0x22, 0x17, 0x41, 0x94, 0x32, 0x36
};
static void EPD_2IN9_Lut(const unsigned char *lut)
{
// SSD1680A uses 233 LUT.
// 0, wareform setting
EPD_SSD_SendCommand(0x32);
for (int j=0; j<4; j++) { // LUT0-3
// wave setting
for (int i=0; i<7; i++) {
EPD_SSD_SendData(lut[j*7+i]);
}
// no config
for (int i=0; i<7*7; i++) {
EPD_SSD_SendData(0x00);
}
}
// 4 * (7+49) = 224
// 224, FR
// 2: 50hz, 3: 75Hz, 4: 100Hz, 5: 125Hz
EPD_SSD_SendData(lut[28]);
// 225, XON
EPD_SSD_SendData(0x00);
EPD_SSD_SendData(0x00);
// EOPT VGH VSH1 VSH2 VSL VCOM
// 3F 03 04 2C
// 22 -20v 15v 3v -15v
// 0x22, 0x17, 0x41, 0x94, 0x32, 0x36
// 227, gate voltage
EPD_SSD_SendCommand(0x3F);
EPD_SSD_SendData(lut[29]);
EPD_SSD_SendCommand(0x03);
EPD_SSD_SendData(lut[30]);
// 229, source voltage
EPD_SSD_SendCommand(0x04);
EPD_SSD_SendData(lut[31]); // VSH
EPD_SSD_SendData(lut[32]); // VSH2
EPD_SSD_SendData(lut[33]); // VSL
// 232, VCOM
EPD_SSD_SendCommand(0x2C);
EPD_SSD_SendData(lut[34]);
}
static void EPD_2IN9_Lut_ById(int idx)
{
uint8_t buf[LUT_LITE_LEN];
const uint8_t *lut = buf;
int rc = EPD_SNV_LoadLut(idx, buf, LUT_LITE_LEN);
if (rc != SUCCESS) {
// load default if no lut found.
switch (idx) {
case 0:
lut = lut_lite_fast_bw;
break;
case 1:
lut = lut_lite_gray8_bwr;
break;
default:
lut = NULL;
break;
}
}
if (lut) {
EPD_2IN9_Lut(lut);
}
}
static void EPD_2IN9_SoftReset()
{
@@ -244,18 +368,48 @@ void EPD_2IN9_Sleep(void)
EPD_SSD_SendData(0x01); // 01: mode 1, 11: mode 2
}
void EPD_SSD_Update(void)
void EPD_2IN9_Clear(void)
{
static time_t last = 0;
// wakeup EPD
EPD_SSD_Reset();
time_t now = time(NULL);
if (last && ((now % 60) != 0)) {
// soft reset
EPD_2IN9_SoftReset();
// clear
EPD_2IN9_BWR(EPD_WIDTH, EPD_HEIGHT, 0, 0);
EPD_2IN9_WriteRam(NULL, EPD_WIDTH, EPD_HEIGHT, 0, 0, 0);
EPD_2IN9_WriteRam(NULL, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
// display
EPD_2IN9_Display(0xf7); // c7: by REG f7: by OTP b1: no display
// wait & sleep
EPD_SSD_WaitBusy(15 * 1000);
EPD_2IN9_Sleep();
}
void EPD_2IN9_Update_Clock(void)
{
time_t now;
time(&now);
if (clock_last && ((now % 60) != 0)) {
return;
}
last = now;
// adjust TZ offset
now += utc_offset_mins * 60;
// get localtime
struct tm *l = localtime(&now);
// full update on first start
bool full_upd = (clock_last == 0 || l->tm_min == 0) ? true : false;
// clock started.
clock_last = 1;
// wakeup EPD
EPD_SSD_Reset();
@@ -299,9 +453,8 @@ void EPD_SSD_Update(void)
}
// full update every 30 mins
bool full_upd = (l->tm_min == 0) ? true : false;
EPD_2IN9_BWR(EPD_WIDTH, EPD_HEIGHT, 0, 0);
if (!full_upd) EPD_2IN9_Lut(lut_full_bwr);
if (!full_upd) EPD_2IN9_Lut_ById(0);
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 0);
EPD_2IN9_WriteRam(NULL, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
@@ -314,8 +467,98 @@ void EPD_SSD_Update(void)
return;
}
void EPD_2IN9_Update_Image()
{
//static uint8_t last_step = 0;
uint8_t step = epd_step;
switch (step) {
case EPD_CMD_CLR:
EPD_2IN9_Clear();
break;
case EPD_CMD_MODE:
EPD_2IN9_Clear();
break;
case EPD_CMD_RST: // reset
// wakeup EPD
EPD_SSD_Reset();
// soft reset
EPD_2IN9_SoftReset();
// ready BWR
EPD_2IN9_BWR(EPD_WIDTH, EPD_HEIGHT, 0, 0);
//EPD_2IN9_Lut(lut_lite_gray8_bwr);
break;
case EPD_CMD_BW: // write BW ram
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 0);
break;
case EPD_CMD_RED: // write Red ram
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
break;
case EPD_CMD_FILL: { // write ram with color
uint8_t color = epd_step_data[0];
if (color == 1) { // red
memset(epd_buffer, 0xff, EPD_WIDTH*EPD_HEIGHT/8);
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 1);
} else {
memset(epd_buffer, 0, EPD_WIDTH*EPD_HEIGHT/8);
EPD_2IN9_WriteRam(epd_buffer, EPD_WIDTH, EPD_HEIGHT, 0, 0, 0);
}
break;
}
case EPD_CMD_DP: { // master
// display lut select
uint8_t dpm = epd_step_data[0];
if (dpm == 1) { // lut1
EPD_2IN9_Lut_ById(0);
} else if (dpm == 2) { // lut2
EPD_2IN9_Lut_ById(1);
} else if (dpm == 3) { // lut3
EPD_2IN9_Lut_ById(2);
} else if (dpm == 0xff) { // user ble lut
EPD_2IN9_Lut(ble_data);
}
// otherwise using full lut.
EPD_2IN9_Display(dpm ? 0xc7: 0xf7); // fast display
EPD_SSD_WaitBusy(15 * 1000);
EPD_2IN9_Sleep();
break;
}
}
// done
if (step == epd_step) {
epd_step = EPD_CMD_NC;
}
}
int EPD_SSD_Update(void)
{
if (epd_mode == EPD_MODE_IMG) {
EPD_2IN9_Update_Image();
// stop tick clock
return 0;
}
// default mode
EPD_2IN9_Update_Clock();
// need tick clock
return 1;
}
void EPD_SSD_Init(void)
{
EPD_SSD_Reset();
// if the rtc ahead 10 seconds per day (24 hours)
// ICALL still using 0x8000 (32768 ticks) for 1 seconds,
// (0x8000 + x) / 0x8000 = (24 * 3600) / (24 * 3600 - 10)
// 32768 * 10 / (24 * 3600) = 3.793
RTC_SetCollaborate(epd_rtc_collab);
}

View File

@@ -7,7 +7,11 @@
#include <ti/sysbios/knl/Task.h> // Task_sleep
#include <driverlib/cpu.h> // CPUDelay
#include <osal.h>
#include <osal_snv.h> // osal_snv_write
#include "epd_driver.h"
#include "task_epd.h"
// gpio setting
static PIN_Handle GPIOHandle = NULL;
@@ -29,14 +33,32 @@ uint8_t epd_buffer[EPD_BUF_MAX];
// debug only
int lut_size;
// local time to UTC time offset, in minuts.
int32_t utc_offset_mins = 8 * 60; // default is UTC+8
// battery voltage, in frac <3.8>
uint16_t epd_battery;
uint16_t epd_battery = -1;
// in degree celcius, read from EPD.
int8_t epd_temperature;
// local time to UTC time offset, in minuts.
int32_t utc_offset_mins = 8 * 60; // default is UTC+8
// display mode (default is clock)
uint8_t epd_mode = EPD_MODE_CLOCK;
// rtc collaboration, default -3.
int8_t epd_rtc_collab = -3;
// EPD BLE step
uint8_t epd_step = EPD_CMD_NC;
uint8_t epd_step_data[EPD_STEP_DATA_LEN]; // store parameters
// the clock refesh on change
uint8_t clock_last = 0;
// BLE data buffer
uint8_t ble_data[BLE_DATA_MAX];
uint8_t ble_data_len = 0;
uint8_t ble_data_cur = 0;
/*
* Device APIs
@@ -226,6 +248,39 @@ uint8_t EPD_BATT_Percent(void)
return 0;
}
/* RTC minor adjustment support.
*/
#if 1
#include <driverlib/aon_wuc.h>
#include <driverlib/../inc/hw_aux_wuc.h>
// collaborate rtc tick, slow down(<0), speed up(>0)
void RTC_SetCollaborate( int8_t rtc_collab )
{
uint32_t subSecInc = (0x8000 + (int)rtc_collab) << 8;
// Loading a new RTCSUBSECINC value is done in 5 steps:
// 1. Write bit[15:0] of new SUBSECINC value to AUX_WUC_O_RTCSUBSECINC0
// 2. Write bit[23:16] of new SUBSECINC value to AUX_WUC_O_RTCSUBSECINC1
// 3. Set AUX_WUC_RTCSUBSECINCCTL_UPD_REQ
// 4. Wait for AUX_WUC_RTCSUBSECINCCTL_UPD_ACK
// 5. Clear AUX_WUC_RTCSUBSECINCCTL_UPD_REQ
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINC0 ) = (( subSecInc ) & 0xFFFF );
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINC1 ) = (( subSecInc >> 16 ) & 0xFF );
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL ) = 1;
while( ! ( HWREGBITW( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL, AUX_WUC_RTCSUBSECINCCTL_UPD_ACK_BITN )));
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL ) = 0;
// save
epd_rtc_collab = rtc_collab;
}
int8_t RTC_GetCollaborate( void )
{
return epd_rtc_collab;
}
#endif
// Select a EPD
#if defined(EPD_2IN13_SSD1680)
@@ -241,52 +296,203 @@ uint8_t EPD_BATT_Percent(void)
#endif
#if 1
#include <driverlib/aon_wuc.h>
#include <driverlib/../inc/hw_aux_wuc.h>
// collaborate rtc tick, slow down(<0), speed up(>0)
void RTC_Collaborate( int rtc_collab )
// do command from BLE
void EPD_Command(const uint8_t *cmd, int cmd_len)
{
uint32_t subSecInc = (0x8000 + rtc_collab) << 8;
// Loading a new RTCSUBSECINC value is done in 5 steps:
// 1. Write bit[15:0] of new SUBSECINC value to AUX_WUC_O_RTCSUBSECINC0
// 2. Write bit[23:16] of new SUBSECINC value to AUX_WUC_O_RTCSUBSECINC1
// 3. Set AUX_WUC_RTCSUBSECINCCTL_UPD_REQ
// 4. Wait for AUX_WUC_RTCSUBSECINCCTL_UPD_ACK
// 5. Clear AUX_WUC_RTCSUBSECINCCTL_UPD_REQ
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINC0 ) = (( subSecInc ) & 0xFFFF );
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINC1 ) = (( subSecInc >> 16 ) & 0xFF );
// buffer recv position (max 64k)
static uint16_t buf_cur = 0;
bool need_update = 0;
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL ) = 1;
while( ! ( HWREGBITW( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL, AUX_WUC_RTCSUBSECINCCTL_UPD_ACK_BITN )));
HWREG( AUX_WUC_BASE + AUX_WUC_O_RTCSUBSECINCCTL ) = 0;
switch (cmd[0]) {
// clear screen
case EPD_CMD_CLR:
need_update = 1;
break;
// display mode change
case EPD_CMD_MODE:
if (epd_mode != cmd[1]) {
clock_last = 0; // stop clock
epd_mode = cmd[1];
need_update = 1;
}
break;
// recv epd_buffer
case EPD_CMD_BUF:
buf_cur = 0;
// pass through
case EPD_CMD_BUF_CONT: {
uint16_t len = cmd_len - 1;
len = MIN(len, (EPD_BUF_MAX - buf_cur));
memcpy(&epd_buffer[buf_cur], &cmd[1], len);
buf_cur += len;
break;
}
// recv LUT
case EPD_CMD_LUT:
// write LUT to ble_data
ble_data_len = cmd_len - 1;
memcpy(ble_data, &cmd[1], ble_data_len);
break;
// EPD Reset
case EPD_CMD_RST:
need_update = 1;
break;
// write BW ram
case EPD_CMD_BW:
need_update = 1;
break;
// write RED ram
case EPD_CMD_RED:
need_update = 1;
break;
// Display
case EPD_CMD_DP:
epd_step_data[0] = cmd[1];
need_update = 1;
break;
// fill ram with color
case EPD_CMD_FILL:
epd_step_data[0] = cmd[1];
need_update = 1;
break;
// put data to buffer
case EPD_CMD_BUF_PUT: {
// cmd, idx, data ...
uint8_t idx = cmd[1];
uint8_t len = MIN(cmd_len - 2, BLE_DATA_MAX - idx);
// calculate data length
ble_data_len = idx + len;
// save data
memcpy(&ble_data[idx], &cmd[2], len);
break;
}
// get data from buffer, data send by ReadAttr
case EPD_CMD_BUF_GET: {
// cmd, idx
ble_data_cur = cmd[1];
break;
}
// write ble_data to snv
case EPD_CMD_SNV_WRITE: {
uint8_t id = cmd[1];
if (ble_data_len != 0) {
osal_snv_write(id, ble_data_len, ble_data);
}
break;
}
// read snv to ble_data
case EPD_CMD_SNV_READ: {
// cmd, id, len
uint8_t id = cmd[1];
uint8_t len = cmd[2];
uint8_t rc = osal_snv_read(id, len, ble_data);
ble_data_len = (rc == SUCCESS) ? len : 0;
break;
}
// save configuration to snv
case EPD_CMD_SAVE_CFG:
EPD_SNV_SaveCfg();
break;
default:
// ignore the command.
return;
}
// save step
epd_step = cmd[0];
// notifiy EPDTask if needs
if (need_update) {
EPDTask_Update();
}
}
#endif
// get EPD state
int EPD_State(uint8_t *buf, uint8_t size)
{
return 0;
}
// load configuration from SNV
int EPD_SNV_LoadCfg()
{
struct epd_snv_cfg cfg;
uint8_t rc = osal_snv_read(EPD_SNV_CFG, sizeof(cfg), &cfg);
if (rc == SUCCESS) {
epd_mode = cfg.u.cfg.mode;
utc_offset_mins = cfg.u.cfg.utc_offset;
epd_rtc_collab = cfg.u.cfg.rtc_collab;
}
return rc;
}
// save configuration to SNV
int EPD_SNV_SaveCfg()
{
struct epd_snv_cfg cfg;
cfg.u.cfg.mode = epd_mode;
cfg.u.cfg.utc_offset = utc_offset_mins;
cfg.u.cfg.rtc_collab = epd_rtc_collab;
return osal_snv_write(EPD_SNV_CFG, sizeof(cfg), &cfg);
}
// load lut from SNV
int EPD_SNV_LoadLut(int index, uint8_t *lut, int len)
{
uint8_t rc;
return osal_snv_read(EPD_SNV_LUT1 + index, len, lut);
}
// save lut to SNV
int EPD_SNV_SaveLut(int index, const uint8_t *lut, int len)
{
uint8_t rc;
return osal_snv_write(EPD_SNV_LUT1 + index, len, (void *)lut);
}
// should be only called once!
void EPD_Init()
{
GPIOHandle = PIN_open(&GPIOState, GPIOTable);
// test LUT size
// test LUT size, different EPD has different LUT size.
//lut_size = EPD_LUT_Detect();
// if the rtc ahead 10 seconds per day (24 hours)
// ICALL still using 0x8000 (32768 ticks) for 1 seconds,
// (0x8000 + x) / 0x8000 = (24 * 3600) / (24 * 3600 - 10)
// 32768 * 10 / (24 * 3600) = 3.793
//RTC_Collaborate(-3);
#if 0
uint8_t buf[16] = {'a', 'b', 'c', 'd', };
uint8_t rc = osal_snv_read(0x80, 16, buf);
if (rc != SUCCESS) {
memcpy(buf, "hello world.", 13);
osal_snv_write(0x80, 16, buf);
}
#endif
// load from snv
EPD_SNV_LoadCfg();
// init EPD
EPD_SSD_Init();
}
void EPD_Update()
int EPD_Update()
{
// update battery level
epd_battery = AONBatMonBatteryVoltageGet();
epd_battery = MIN(epd_battery, AONBatMonBatteryVoltageGet());
// update Display
EPD_SSD_Update();
return EPD_SSD_Update();
}

View File

@@ -1,7 +1,7 @@
#ifndef _EPD_DRIVER_H_
#define _EPD_DRIVER_H_
#include <stdint.h>
#include <stdint.h> // uint8_t
// select a EPD device
//#define EPD_2IN13_SSD1680
@@ -24,7 +24,75 @@ extern int32_t utc_offset_mins;
// public sensor values
extern int8_t epd_temperature; // in degree celsius, +/-127
extern uint16_t epd_battery; // in (3.8) frac
extern uint16_t epd_battery; // in (3.8) frac, minium value
// display mode
extern uint8_t epd_mode;
#define EPD_MODE_CLOCK 0 // realtime clock
#define EPD_MODE_IMG 1 // static image
// TBD: split image display to steps in a dirty way.
extern uint8_t epd_step;
#define EPD_STEP_DATA_LEN 4
extern uint8_t epd_step_data[EPD_STEP_DATA_LEN];
// saved clock tick.
extern uint8_t clock_last;
// ble data buffer
#define BLE_DATA_MAX 256
extern uint8_t ble_data[BLE_DATA_MAX]; // ble data, a 256 bytes buffer
extern uint8_t ble_data_len; // indicate the length of ble_data
extern uint8_t ble_data_cur; // indicate the current position of ble_data
// EPD service commands
enum EPD_CMD {
EPD_CMD_NC = 0, // nothing
// keep order
EPD_CMD_CLR, // clear screen
EPD_CMD_MODE, // set display mode
EPD_CMD_BUF, // first receive to epd_buffer
EPD_CMD_BUF_CONT, // continue write to epd_buffer
EPD_CMD_LUT, // set lut
EPD_CMD_RST, // reset EPD
EPD_CMD_BW, // write EPD Black/White
EPD_CMD_RED, // write EPD Red
EPD_CMD_DP, // EPD display
EPD_CMD_FILL, // fill ram with color
EPD_CMD_BUF_PUT, // put data to buffer
EPD_CMD_BUF_GET, // get data from buffer
EPD_CMD_SNV_WRITE, // write ble_data to snv
EPD_CMD_SNV_READ, // read snv to ble_data
EPD_CMD_SAVE_CFG, // save configuration to snv, rtc collaborate, utc offset, etc ...
EPD_CMD_MAX
};
// CC2640r2 SNV user area, 0x80 - 0x8f
enum EPD_SNV {
EPD_SNV_CFG = 0x80, // configuration
EPD_SNV_LUT1, // LUT1, fast bw
EPD_SNV_LUT2, // LUT2, gray bwr
EPD_SNV_LUT3, // LUT3
EPD_SNV_MAX
};
// EPD configuration in SNV
struct epd_snv_cfg {
union {
struct {
uint8_t mode; // saved display mode
int8_t rtc_collab; // saved rtc collaborate
int16_t utc_offset; // saved utc offset in minutes
} cfg;
uint8_t raw[32];
} u;
};
/*
* <int.frac> format size (3.8) bits.
* int for 0-3 voltage
@@ -34,9 +102,17 @@ extern uint16_t epd_battery; // in (3.8) frac
#define INTFRAC_mV(x) ((x&0xff)*125/32)
#define INTFRAC2MV(x) (INTFRAC_mV(x)+(INTFRAC_V(x)*1000))
// Macro
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? a : b)
#endif
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? a : b)
#endif
// the driver public,
void EPD_Init();
void EPD_Update();
int EPD_Update();
void EPD_SSD_Reset();
void EPD_SSD_SendCommand(uint8_t reg);
void EPD_SSD_SendData(uint8_t data);
@@ -46,8 +122,21 @@ bool EPD_SSD_IsBusy();
void EPD_SSD_WaitBusy(uint32_t ms);
uint8_t EPD_BATT_Percent();
// RTC
void RTC_SetCollaborate( int8_t rtc_collab );
int8_t RTC_GetCollaborate();
// SNV
int EPD_SNV_LoadCfg();
int EPD_SNV_SaveCfg();
int EPD_SNV_LoadLut(int index, uint8_t *lut, int len);
int EPD_SNV_SaveLut(int index, const uint8_t *lut, int len);
// API for EPD commands
void EPD_Command(const uint8_t *cmd, int cmd_len);
// for epd_inch.c shoule implement,
void EPD_SSD_Init();
void EPD_SSD_Upate();
int EPD_SSD_Upate();
#endif

View File

@@ -13,6 +13,7 @@
#include "epd_driver.h" // epd_battery, epd_temperature
#include "epd_service.h"
#include "task_epd.h" // EPDTask_Update
// EPD_Service Service UUID
CONST uint8 EpdServiceUUID[ATT_BT_UUID_SIZE] = {
@@ -40,29 +41,39 @@ CONST uint8 EpdRtcCollabUUID[ATT_BT_UUID_SIZE] = {
LO_UINT16(EPD_RTC_COLLAB_UUID), HI_UINT16(EPD_RTC_COLLAB_UUID),
};
CONST uint8 EpdRxTxUUID[ATT_BT_UUID_SIZE] = {
LO_UINT16(EPD_RXTX_UUID), HI_UINT16(EPD_RXTX_UUID),
};
static EpdServiceCBs_t *pAppCBs = NULL;
static gattCharCfg_t *EpdDataConfig;
// Service declaration
static CONST gattAttrType_t EpdServiceDecl = { ATT_BT_UUID_SIZE, EpdServiceUUID };
static uint8 EpochProps = GATT_PROP_READ | GATT_PROP_WRITE;
static uint8 EpochVal[4] = {0};
//static uint8 EpochDesc[] = "Unix Epoch";
static uint8 EpochProps = GATT_PROP_READ | GATT_PROP_WRITE;
static uint32 EpochLastVal = 0; // last value of Unix Epoch
static uint8 UtcOffProps = GATT_PROP_READ | GATT_PROP_WRITE;
//static uint8 UtcOffDesc[] = "UTC Offset Mins";
static int8 UtcOffVal[4] = {0};
static uint8 UtcOffProps = GATT_PROP_READ | GATT_PROP_WRITE;
//static int8 UtcOffVal[4] = {0};
static uint8 BattProps = GATT_PROP_READ;
//static uint8 BattDesc[] = "Battery mv";
static uint8 BattVal[2] = {0};
static uint8 BattProps = GATT_PROP_READ;
//static uint8 BattVal[2] = {0};
static uint8 TempProps = GATT_PROP_READ;
//static uint8 TempDesc[] = "Temperature";
static int8 TempVal[1] = {0};
static uint8 TempProps = GATT_PROP_READ;
//static int8 TempVal[1] = {0};
// RTC Collaboration
static uint8 RtcCollabProps = GATT_PROP_READ | GATT_PROP_WRITE;
static int8 RtcCollabVal[1] = {0};
//static int8 RtcCollabVal[1] = {0};
// RxTx service
static uint8 RxTxProps = GATT_PROP_READ | GATT_PROP_WRITE;
//static uint8 RxTxBuf[64];
static gattAttribute_t EpdServiceAttrTbl[] =
{
@@ -86,7 +97,7 @@ static gattAttribute_t EpdServiceAttrTbl[] =
{ ATT_BT_UUID_SIZE, EpdEpochUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
EpochVal
NULL
},
// Characteristic Declaration
@@ -101,7 +112,7 @@ static gattAttribute_t EpdServiceAttrTbl[] =
{ ATT_BT_UUID_SIZE, EpdUtcOffsetUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
UtcOffVal
NULL
},
// Characteristic Declaration
@@ -116,7 +127,7 @@ static gattAttribute_t EpdServiceAttrTbl[] =
{ ATT_BT_UUID_SIZE, EpdBattUUID },
GATT_PERMIT_READ,
0,
BattVal
NULL
},
// Characteristic Declaration
@@ -131,7 +142,7 @@ static gattAttribute_t EpdServiceAttrTbl[] =
{ ATT_BT_UUID_SIZE, EpdTempUUID },
GATT_PERMIT_READ,
0,
TempVal
NULL
},
// Characteristic Declaration
@@ -146,7 +157,22 @@ static gattAttribute_t EpdServiceAttrTbl[] =
{ ATT_BT_UUID_SIZE, EpdRtcCollabUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
RtcCollabVal
NULL
},
// Characteristic Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&RxTxProps
},
// Characteristic Value
{
{ ATT_BT_UUID_SIZE, EpdRxTxUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
NULL
},
};
@@ -231,12 +257,7 @@ bStatus_t EPDService_GetParameter(uint8_t param, uint16_t *len, void *value)
{
bStatus_t ret = SUCCESS;
switch (param) {
#if 0
case EPD_EPOCH_ID:
*len = 4;
memcpy(value, EpochVal, *len);
break;
#endif
default:
ret = INVALIDPARAMETER;
break;
@@ -244,33 +265,6 @@ bStatus_t EPDService_GetParameter(uint8_t param, uint16_t *len, void *value)
return ret;
}
#if 0
static uint8_t EPDService_findCharParamId(gattAttribute_t *pAttr)
{
#if 0
// Is this a Client Characteristic Configuration Descriptor?
if(ATT_BT_UUID_SIZE == pAttr->type.len && GATT_CLIENT_CHAR_CFG_UUID ==
*(uint16_t *)pAttr->type.uuid) {
return (EPDService_findCharParamId(pAttr - 1)); // Assume the value attribute precedes CCCD and recurse
} elif
#endif
if (ATT_BT_UUID_SIZE == pAttr->type.len) {
uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
switch (uuid) {
case EPD_EPOCH_UUID:
return EPD_EPOCH_ID;
case EPD_UTC_OFFSET_UUID:
return EPD_UTC_OFFSET_ID;
case EPD_BATT_UUID:
return EPD_BATT_ID;
case EPD_TEMP_UUID:
return EPD_TEMP_ID;
}
}
return 0xFF; // Not found. Return invalid.
}
#endif
static bStatus_t utilExtractUuid16(gattAttribute_t *pAttr, uint16_t *pUuid)
{
bStatus_t status = SUCCESS;
@@ -336,12 +330,23 @@ static bStatus_t EPDService_ReadAttrCB(uint16_t connHandle,
}
case EPD_RTC_COLLAB_UUID: {
int8_t v = RtcCollabVal[0];
int8_t v = RTC_GetCollaborate();
*pLen = sizeof(v);
memcpy(pValue, &v, *pLen);
break;
}
case EPD_RXTX_UUID: {
// send ble_data to BLE.
uint8_t idx = ble_data_cur;
uint8_t len = MIN(ble_data_len - idx, maxLen - 1);
// index, data ...
pValue[0] = idx;
memcpy(&pValue[1], &ble_data[idx], len);
*pLen = len + 1;
break;
}
default:
return ATT_ERR_ATTR_NOT_FOUND;
}
@@ -369,6 +374,11 @@ static bStatus_t EPDService_WriteAttrCB(uint16_t connHandle,
if (len == 4) {
uint32_t t = *(uint32_t*)pValue;
Seconds_set(t);
EpochLastVal = t;
// notify EPD to refresh
clock_last = 0;
EPDTask_Update();
}
break;
}
@@ -386,12 +396,18 @@ static bStatus_t EPDService_WriteAttrCB(uint16_t connHandle,
case EPD_RTC_COLLAB_UUID: {
if (len == 1) {
int8_t v = *(int8_t*)pValue;
RTC_Collaborate(v);
RtcCollabVal[0] = v;
RTC_SetCollaborate(v);
}
break;
}
case EPD_RXTX_UUID: {
if (len < 64) {
EPD_Command(pValue, len);
}
break;
}
case GATT_CLIENT_CHAR_CFG_UUID:
status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len, offset, GATT_CLIENT_CFG_NOTIFY);
break;

View File

@@ -19,7 +19,10 @@
#define EPD_TEMP_UUID 0xFFF4
// RTC collaboration value
#define EPD_RTC_COLLAB_UUID 0xFFF5
#define EPD_RTC_COLLAB_UUID 0xFFF5
// EPD RXTX service
#define EPD_RXTX_UUID 0xFFFE
// Callback when a characteristic value has changed
typedef void (*EpdServiceChange_t)(uint16_t connHandle, uint8_t paramID,

View File

@@ -30,23 +30,24 @@
#include "task_ble.h"
// Advertising interval when device is discoverable (units of 625us, 160=100ms)
#define DEFAULT_ADVERTISING_INTERVAL 160
#define DEFAULT_ADVERTISING_INTERVAL 1600
// General discoverable mode: advertise indefinitely
#define DEFAULT_DISCOVERABLE_MODE GAP_ADTYPE_FLAGS_GENERAL
// Minimum connection interval (units of 1.25ms, 80=100ms) for automatic
// Minimum connection interval (units of 1.25ms, 80=100ms, min=6) for automatic
// parameter update request
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 80
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 16
// Maximum connection interval (units of 1.25ms, 800=1000ms) for automatic
// Maximum connection interval (units of 1.25ms, 800=1000ms, max=3200) for automatic
// parameter update request
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL 800
// Slave latency to use for automatic parameter update request
#define DEFAULT_DESIRED_SLAVE_LATENCY 0
#define DEFAULT_DESIRED_SLAVE_LATENCY 3
// Supervision timeout value (units of 10ms, 1000=10s) for automatic parameter
// MEETS : CONN_TIMEOUT > (1 + SLAVE_LATENCY) * CONN_INTERVAL
// Supervision timeout value (units of 10ms, 1000=10s, 10-3200) for automatic parameter
// update request
#define DEFAULT_DESIRED_CONN_TIMEOUT 1000
@@ -55,10 +56,10 @@
#define DEFAULT_ENABLE_UPDATE_REQUEST GAPROLE_LINK_PARAM_UPDATE_WAIT_REMOTE_PARAMS
// Connection Pause Peripheral time value (in seconds)
#define DEFAULT_CONN_PAUSE_PERIPHERAL 6
#define DEFAULT_CONN_PAUSE_PERIPHERAL 5
// How often to perform periodic event (in msec)
#define SBP_PERIODIC_EVT_PERIOD 5000
//#define SBP_PERIODIC_EVT_PERIOD 5000
// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT 0x0001
@@ -78,11 +79,13 @@
// Internal Events for RTOS application
#define SBP_ICALL_EVT ICALL_MSG_EVENT_ID // Event_Id_31
#define SBP_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30
#define SBP_RXTX_QUEUE_EVT Event_Id_01
//#define SBP_PERIODIC_EVT Event_Id_00
// Bitwise OR of all events to pend on
#define SBP_ALL_EVENTS (SBP_ICALL_EVT | \
SBP_QUEUE_EVT)
SBP_QUEUE_EVT | \
SBP_RXTX_QUEUE_EVT)
// Row numbers for two-button menu
#define SBP_ROW_RESULT TBM_ROW_APP
@@ -160,18 +163,12 @@ static uint8_t advertData[] =
GAP_ADTYPE_16BIT_MORE, // some of the UUID's, but not all
LO_UINT16(EPD_SERVICE_SERV_UUID),
HI_UINT16(EPD_SERVICE_SERV_UUID)
#else
16, 0x16, // spec
LO_UINT16(EPD_SERVICE_SERV_UUID), HI_UINT16(EPD_SERVICE_SERV_UUID),
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mac address
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // data
0x07,
0x03, // length of this data
GAP_ADTYPE_16BIT_MORE, // some of the UUID's, but not all
LO_UINT16(EPD_SERVICE_SERV_UUID),
HI_UINT16(EPD_SERVICE_SERV_UUID)
#endif
};
@@ -533,6 +530,11 @@ static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
}
}
// RXTX events
if (events & SBP_RXTX_QUEUE_EVT) {
}
// If RTOS queue is not empty, process app message.
if (events & SBP_QUEUE_EVT) {
while (!Queue_empty(appMsgQueue)) {
@@ -803,10 +805,10 @@ static void SimpleBLEPeripheral_processStateChangeEvt(gaprole_States_t newState)
case GAPROLE_STARTED:
{
uint8_t ownAddress[B_ADDR_LEN];
uint8_t systemId[DEVINFO_SYSTEM_ID_LEN];
GAPRole_GetParameter(GAPROLE_BD_ADDR, ownAddress);
#if 0
uint8_t systemId[DEVINFO_SYSTEM_ID_LEN];
// use 6 bytes of device address for 8 bytes of system ID value
systemId[0] = ownAddress[0];
systemId[1] = ownAddress[1];
@@ -826,6 +828,7 @@ static void SimpleBLEPeripheral_processStateChangeEvt(gaprole_States_t newState)
// Display device address
Display_print0(dispHandle, SBP_ROW_BDADDR, 0, Util_convertBdAddr2Str(ownAddress));
Display_print0(dispHandle, SBP_ROW_ROLESTATE, 0, "Initialized");
#endif
}
break;

View File

@@ -61,6 +61,11 @@ void TaskEPD_createTask(void)
Task_construct(&EPDTask, TaskEPD_taskFxn, &taskParams, NULL);
}
void EPDTask_Update(void)
{
Event_post(syncEvent, EPDTASK_EVENT_PERIODIC);
}
static void EPDTask_clockHandler(UArg arg)
{
Event_post(syncEvent, arg);
@@ -72,9 +77,14 @@ void TaskEPD_taskInit(void)
// so that the application can send and receive messages.
ICall_registerApp(&selfEntity, &syncEvent);
// second period timer
Util_constructClock(&periodicClock, EPDTask_clockHandler,
1000, 0, false, EPDTASK_EVENT_PERIODIC);
// init EPD
EPD_Init();
// start timer
Util_startClock(&periodicClock);
}
@@ -88,10 +98,9 @@ void TaskEPD_taskFxn(UArg a0, UArg a1)
events = Event_pend(syncEvent, Event_Id_NONE, EPDTASK_EVENT_ALL, ICALL_TIMEOUT_FOREVER);
if (events & EPDTASK_EVENT_PERIODIC) {
EPD_Update();
Util_startClock(&periodicClock);
if (EPD_Update()) {
Util_startClock(&periodicClock);
}
}
}
}

View File

@@ -6,4 +6,7 @@ typedef void (*EpdResponseCallback)(uint8_t event, uint8_t *buf, uint8_t len);
void EPDTask_RegisterResponseCallback(EpdResponseCallback callback);
void TaskEPD_createTask(void);
// api for event_post
void EPDTask_Update(void);
#endif

View File

@@ -32,7 +32,7 @@
--cmd_file=${SRC_BLE_DIR}/config/build_components.opt
--cmd_file=${SRC_BLE_DIR}/config/factory_config.opt
--cmd_file=${WORKSPACE_LOC}/ble5_simple_peripheral_cc2640r2lp_stack_library/TOOLS/build_config.opt
-mv7M3 -O4 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
-mv7M3 -O2 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
-DDeviceFamily_CC26X0R2
-DBOARD_DISPLAY_USE_LCD=0
-DBOARD_DISPLAY_USE_UART=0

View File

@@ -36,7 +36,7 @@
--cmd_file=${SRC_BLE_DIR}/config/build_components.opt
--cmd_file=${SRC_BLE_DIR}/config/factory_config.opt
--cmd_file=${PROJECT_LOC}/TOOLS/build_config.opt
-mv7M3 -O4 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
-mv7M3 -O2 --opt_for_speed=0 --code_state=16 --abi=eabi -me -g --c99 --gcc --gen_func_subsections=on --display_error_number --diag_wrap=off
-D${XDC_SYMBOLS}
-DCC26XX
-DCC26XX_R2

10
tools/ble5_ctrl.py Normal file
View File

@@ -0,0 +1,10 @@
import asyncio
from bleak import BleakScanner
async def main():
devices = await BleakScanner.discover()
for d in devices:
print(d, d.metadata)
BleakScanner.discovered_devices_and_advertisement_data = True
asyncio.run(main())

BIN
tools/bwr_gray8.act Normal file

Binary file not shown.

View File

@@ -1,28 +1,42 @@
<html lang="zh-CN">
<head>
<style>
div {
padding: 12px;
margin: 2 auto;
}
</style>
</head>
<script>
<body>
<script>
let bleDevice;
let gattServer;
let Theservice;
let epdService;
let epochCharacter;
async function sleep(ms) {
await new Promise(resolve => setTimeout(resolve, ms));
}
async function doConnect() {
if (gattServer != null && gattServer.connected) {
if (bleDevice != null && bleDevice.gatt.connected)
bleDevice.gatt.disconnect();
}
else {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: ['C26_'] }],
optionalServices: [
0xfff0,
],
//acceptAllDevices: true
});
await bleDevice.addEventListener('gattserverdisconnected', disconnect);
await connect();
}
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: ['C26_'] }],
optionalServices: [
0xfff0,
],
//acceptAllDevices: true
});
await bleDevice.addEventListener('gattserverdisconnected', disconnect);
await connect();
}
}
async function connect() {
@@ -34,66 +48,257 @@
info('> Found EPD service');
epochCharacter = await epdService.getCharacteristic(0xfff1);
document.getElementById("btnConnect").innerHTML = 'Disconnect';
document.getElementById('etagFn').style.visibility='';
document.getElementById('etagFn').style.visibility = '';
}
}
function disconnect() {
bleDevice = null;
epdService = null;
epochCharacter = null;
info('Disconnected.');
document.getElementById("btnConnect").innerHTML = 'Connect';
document.getElementById('etagFn').style.visibility='hidden';
bleDevice = null;
epdService = null;
epochCharacter = null;
info('Disconnected.');
document.getElementById("btnConnect").innerHTML = 'Connect';
document.getElementById('etagFn').style.visibility = 'hidden';
}
async function doSetTime() {
var epoch = Date.now() / 1000 | 0;
var buf = new ArrayBuffer(4);
var arr = new Uint32Array(buf);
arr[0] = epoch;
await epochCharacter.writeValueWithResponse(arr);
info("Write unix epoch: " + epoch);
var epoch = Date.now() / 1000 | 0;
var buf = new ArrayBuffer(4);
var arr = new Uint32Array(buf);
arr[0] = epoch;
await epochCharacter.writeValueWithResponse(arr);
info("Write unix epoch: " + epoch);
}
async function doReadEtag() {
var host_epoch = Date.now() / 1000 | 0;
var host_epoch = Date.now() / 1000 | 0;
// read current time
var chr = await epdService.getCharacteristic(0xfff1);
var epoch = (await chr.readValue()).getUint32(0, 1);
// read current time
var chr = await epdService.getCharacteristic(0xfff1);
var epoch = (await chr.readValue()).getUint32(0, 1);
// read time zone
var chr = await epdService.getCharacteristic(0xfff2);
var tz_min = (await chr.readValue()).getInt32(0, 1);
info(`# host time: ${host_epoch}, diff (${epoch - host_epoch}) seconds.`);
info(`# etag time: ${epoch}, tz: ${tz_min} minutes of UTC.`);
// battery
var chr = await epdService.getCharacteristic(0xfff3);
var batt = (await chr.readValue()).getUint16(0, 1);
// read time zone
var chr = await epdService.getCharacteristic(0xfff2);
var tz_min = (await chr.readValue()).getInt32(0, 1);
info(`# host time: ${host_epoch}, diff (${epoch - host_epoch}) seconds.`);
info(`# etag time: ${epoch}, tz: ${tz_min} minutes of UTC.`);
// Temperature
var chr = await epdService.getCharacteristic(0xfff4);
var temp = (await chr.readValue()).getInt8(0, 1);
info(`# etag sensor: battery(${batt}mv), temperature(${temp}'C). `);
// battery
var chr = await epdService.getCharacteristic(0xfff3);
var batt = (await chr.readValue()).getUint16(0, 1);
// RTC Collaborate
var chr = await epdService.getCharacteristic(0xfff5);
var rtc_collab = (await chr.readValue()).getInt8(0, 1);
info(`# rtc collab: ${rtc_collab} every 1 second.`);
// Temperature
var chr = await epdService.getCharacteristic(0xfff4);
var temp = (await chr.readValue()).getInt8(0, 1);
info(`# etag sensor: battery(${batt}mv), temperature(${temp}'C). `);
// RTC Collaborate
var chr = await epdService.getCharacteristic(0xfff5);
var rtc_collab = (await chr.readValue()).getInt8(0, 1);
info(`# rtc collab: ${rtc_collab} every 1 second.`);
}
async function doRtcCollab() {
var col = prompt("对 32.768kHz 晶振补偿频漂,走时快补偿负数,走时慢补偿正数。可选范围 (-3 ~ 3)", 0);
if (col == null || col < -3 || col > 3) return;
var chr = await epdService.getCharacteristic(0xfff5);
var buf = new ArrayBuffer(1);
var arr = new Int8Array(buf);
arr[0] = parseInt(col);
await chr.writeValueWithResponse(arr);
info(`write RTC collabration: ${col}`);
var col = prompt("对 32.768kHz 晶振补偿频漂,走时快补偿负数,走时慢补偿正数。可选范围 (-3 ~ 3)", 0);
if (col == null || col < -3 || col > 3) return;
var chr = await epdService.getCharacteristic(0xfff5);
var buf = new ArrayBuffer(1);
var arr = new Int8Array(buf);
arr[0] = parseInt(col);
await chr.writeValueWithResponse(arr);
info(`write RTC collabration: ${col}`);
}
async function doTest() {
var chr = await epdService.getCharacteristic(0xfffe);
var buf = new ArrayBuffer(62);
var arr = new Int8Array(buf);
for (var i = 0; i < arr.length; i++) {
arr[i] = i % 8;
}
await chr.writeValueWithResponse(arr);
info(`> write ${arr.length} bytes.`)
//var out = await chr.readValue();
//console.log(out);
}
async function doCmd(cmd, data) {
const epdCmd = {
EPD_CMD_CLR: 1,
EPD_CMD_MODE: 2,
EPD_CMD_BUF: 3,
EPD_CMD_BUF_CONT: 4,
EPD_CMD_LUT: 5,
EPD_CMD_RST: 6,
EPD_CMD_BW: 7,
EPD_CMD_RED: 8,
EPD_CMD_DP: 9,
EPD_CMD_FILL: 10,
EPD_CMD_BUF_PUT: 11,
EPD_CMD_BUF_GET: 12,
EPD_CMD_SNV_WRITE: 13,
EPD_CMD_SNV_READ: 14,
EPD_CMD_SAVE_CFG: 15,
};
var chr = await epdService.getCharacteristic(0xfffe);
switch (cmd) {
case 'clr':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_CLR]));
break;
case 'mode':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_MODE, data == 'image'?0x01:0x00]));
break;
case 'buf':
for (var i = 0; i < data.length; i += 60) {
let arr = [(i == 0 ? epdCmd.EPD_CMD_BUF : epdCmd.EPD_CMD_BUF_CONT)];
arr.push(...data.slice(i, i + 60));
//console.log(arr);
await chr.writeValueWithResponse(Uint8Array.from(arr));
//info(`> buf at ${i} size ${arr.length}`)
}
break;
case 'lut':
let arr = [epdCmd.EPD_CMD_LUT];
arr.push(...data);
await chr.writeValueWithResponse(Uint8Array.from(arr));
break;
case 'rst':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_RST]));
break;
case 'bw':
// write to bw ram
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_BW]));
break;
case 'red':
// write to bw ram
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_RED]));
break;
case 'fill':
// fill ram with black(0) or red(1)
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_FILL, data == 'red' ? 0x01:0x00]));
break;
case 'dp':
// show
let lut = 0;
switch (data) {
case 'gray8':
lut = 1;
break;
case 'user':
lut = 0xff;
break;
case 'full':
default:
lut = 0;
}
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_DP, lut]));
break;
case 'snv_read':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_SNV_READ, 0x80, 16]));
break;
case 'snv_write':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_SNV_WRITE, 0x80]));
break;
case 'buf_get':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_BUF_GET, 0x00]));
break;
case 'buf_put':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_BUF_PUT, 0x00, 0x01, 0x02, 0x03, 0x04]));
break;
case 'save_cfg':
await chr.writeValueWithResponse(Uint8Array.from([epdCmd.EPD_CMD_SAVE_CFG]));
break;
}
info (`> epdCmd.${cmd}`)
}
let ram_bw = [];
let ram_red = [];
function doImageGrey(type) {
const canvas = document.getElementById('canvas');
ram_bw = canvas2grey8(canvas, 'bw');
ram_red = canvas2grey8(canvas, 'red');
}
var step = 0;
async function doUploadImageGray8() {
await doCmd('clr');
await sleep(15*1000);
doImageGrey();
for (var i = 0; i < 8; i++) {
await doCmd('rst');
await sleep(2000);
await doUploadImageRam8('bw');
await doUploadImageRam8('red');
await doCmd('dp', 'gray8');
await sleep(8000);
step++;
}
step = 0;
info('> Upload done.')
}
async function doUploadImage(type) {
if (type == 'gray8') {
await doUploadImageGray8();
} else {
await doCmd('rst');
await sleep(2000);
await doUploadImageRam2('bw');
await doUploadImageRam2('red');
await doCmd('dp', 'full');
await sleep(15*1000);
}
}
async function doUploadImageRam2(type = 'bw') {
const canvas = document.getElementById('canvas');
var arr = canvas2bytes(canvas, type = type);
await doCmd('buf', arr);
await doCmd(type)
}
async function doUploadImageRam8(type = 'bw') {
var ram = type == 'bw' ? ram_bw : ram_red;
// grey byte to bit
var arr = [];
var buffer = [];
for (var x = 0; x < ram.length; x++) {
const n = ram[x] > step ? 1 : 0;
if (type == 'bw') {
buffer.push(n ? 0 : 1);
} else {
buffer.push(n ? 1 : 0);
}
if (buffer.length == 8) {
arr.push(parseInt(buffer.join(''), 2));
buffer = [];
}
}
info(`> write ram ${type} size ${arr.length}, step ${step}`);
console.log(arr);
await doCmd('buf', arr);
await doCmd(type)
}
function info(logTXT) {
@@ -106,20 +311,173 @@
document.getElementById("log").innerHTML = document.getElementById("log").innerHTML.substring(logs_br_position + 4);
}
}
</script>
<p>
<label> Choose </label>
<button id="btnConnect" type="button" onclick="doConnect()">Connect</button>
</p>
<p id="etagFn" style="visibility:hidden;">
<button id="btnReadEtag" type="button" onclick="doReadEtag()">ReadEtag</button>
<button id="btnSetTime" type="button" onclick="doSetTime()">SetTime</button>
<button id="btnRtcCollab" type="button" onclick="doRtcCollab()">RtcCollab</button>
</p>
<p>
<div id="log">
CC2640R2-ETAG Webtool. <br/>
</div>
</p>
async function load_image() {
const image_file = document.getElementById('image_file');
if (image_file.files.length > 0) {
const file = image_file.files[0];
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = URL.createObjectURL(file);
image.onload = function (event) {
URL.revokeObjectURL(this.src);
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
//convert_dithering()
}
}
}
function clear_canvas() {
if (confirm('确认清除屏幕?')) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
function canvas2bytes(canvas, type = 'bw') {
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const arr = [];
let buffer = [];
for (let y = 0; y < canvas.height; y += 8) {
for (let x = 0; x < canvas.width; x++) {
for (let a = 0; a < 8; a++) {
const i = (canvas.width * (y + a) + x) * 4;
if (type !== 'red') {
// 1 for white, 0 for black
// black : 0, 0, 0
buffer.push(imageData.data[i] === 0 && imageData.data[i + 1] === 0 && imageData.data[i + 2] === 0 ? 0 : 1);
} else {
// 1 for red, 0 for white
buffer.push(imageData.data[i] > 0 && imageData.data[i + 1] === 0 && imageData.data[i + 2] === 0 ? 1 : 0);
}
}
arr.push(parseInt(buffer.join(''), 2));
buffer = [];
}
}
return arr;
}
function canvas2grey8(canvas, type = 'bw') {
// each px = 4bit black + 4bit red.
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const arr = [];
let buffer = [];
for (let y = 0; y < canvas.height; y += 8) {
for (let x = 0; x < canvas.width; x++) {
for (let a = 0; a < 8; a++) {
const i = (canvas.width * (y + a) + x) * 4;
const R = imageData.data[i];
const G = imageData.data[i + 1];
const B = imageData.data[i + 2];
let grey = 0; // white
if (R == 255 && G != 255 && B != 255) { // red
grey = (type == 'bw') ? 0 : (510 + 62 - G - B) >> 6;
} else { // gray
grey = (type == 'bw') ? (255 + 31 - R) >> 5 : 0;
}
arr.push(grey);
}
}
}
return arr;
}
async function doMyLut() {
const O = 0b00000000; // VSS
const B = 0b01000000; // VSH1 for Black
const W = 0b10000000; // VSL for white
const R = 0b11000000; // VSH2 for Red
const lut_gray8 = [
// RP A B C D SRAB SRCD
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUTC
0x01, R | 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTR
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUTW
0x01, B | 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTB
0x01, B | 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTB
];
await doCmd('lut', lut_gray8);
}
async function doFill(color) {
await doCmd('rst');
await sleep(2000);
await doCmd('fill', color);
await doCmd('dp', 'full');
await sleep(15*1000);
}
async function doSnvRead(type) {
var chr = await epdService.getCharacteristic(0xfffe);
if (type == 'cfg') {
await doCmd('snv_read', 0x01);
await doCmd('buf_get', 0x00);
var a = await chr.readValue();
console.log(a);
}
}
async function doSnvWrite() {
//var chr = await epdService.getCharacteristic(0xfffe);
await doCmd('buf_put', 0x00);
await doCmd('snv_write', 0x80);
}
</script>
<div>
<label> Choose </label>
<button id="btnConnect" type="button" onclick="doConnect()">Connect</button>
</div>
<div id="etagFn" style="visibility:hidden;">
<button type="button" onclick="doCmd('mode', 'clock')">时钟模式</button>
<button id="btnReadEtag" type="button" onclick="doReadEtag()">ReadEtag</button>
<button id="btnSetTime" type="button" onclick="doSetTime()">SetTime</button>
<button id="btnRtcCollab" type="button" onclick="doRtcCollab()">RtcCollab</button>
<button id="btnTest" type="button" onclick="doTest()">Test</button>
<button type="button" onclick="doCmd('save_cfg')">保存设置</button>
</div>
<div>
<button type="button" onclick="doCmd('mode', 'image')">图片模式</button>
<button type="button" onclick="doCmd('rst')">rst</button>
<button type="button" onclick="doCmd('bw')">bw</button>
<button type="button" onclick="doCmd('red')">red</button>
<button type="button" onclick="doCmd('dp', 'full')">dp</button>
<button type="button" onclick="doMyLut()">LUT</button>
</div>
<div>
<button type="button" onclick="doCmd('clr')">清屏</button>
<button type="button" onclick="doFill('black')">全黑</button>
<button type="button" onclick="doFill('red')">全红</button>
</div>
<div id="canvas-box">
<input type="file" id="image_file" onchange="load_image()" accept=".png,.jpg,.bmp,.webp,.gif">
<br>
<canvas id="canvas" width="296" height="128" style="border: black solid 1px;"></canvas>
<br>
<button onclick="doUploadImage('gray8')">8级灰度</button>
<button onclick="doUploadImage('full')">默认刷新</button>
<br>
</div>
<div>
<button type="button" onclick="doSnvWrite()">写入</button>
<button type="button" onclick="doSnvRead('cfg')">读出</button>
</div>
<div id="log">
CC2640R2-ETAG Webtool. <br>
</div>
</body>
</html>

BIN
tools/pic/black.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

74
tools/pic/gen_bmp.py Normal file
View File

@@ -0,0 +1,74 @@
import os
import struct
def flatten_list(nested_list):
flat_list = []
for item in nested_list:
if isinstance(item, list):
flat_list.extend(flatten_list(item))
else:
flat_list.append(item)
return flat_list
def pkg(fmt, num):
a = struct.pack(fmt, num)
return [ x for x in a ]
def rgb(r, g, b):
return [b, g, r]
def genTestBmp(fpath, type = 0):
width = 296
height = 128
# header
out = [
0x42, 0x4D,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x36, 0x00, 0x00, 0x00, # pixel data offset at 0x36
]
out += [
0x28, 0x00, 0x00, 0x00, # header size 40 bytes
pkg('<I', width),
pkg('<I', height),
0x01, 0x00, # plane 1
0x18, 0x00, # bpp 24 bits
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
]
if type == 0:
for y in range(0, height):
for x in range(0, width):
bg = ((x+1)//33)*32
bg = bg - 1 if bg else 0
if y > height/2:
out += rgb(255, bg, bg)
else:
out += rgb(bg, bg, bg)
# TBD: pad for 4 bytes alignment.
elif type == 1: # all black
for y in range(0, height):
for x in range(0, width):
out += rgb(0, 0, 0)
elif type == 2: # all red
for y in range(0, height):
for x in range(0, width):
out += rgb(255, 0, 0)
with open(fpath, 'wb') as f:
f.write(bytes(flatten_list(out)))
if __name__ == "__main__":
genTestBmp('grey.bmp', type = 0)
genTestBmp('black.bmp', type = 1)
genTestBmp('red.bmp', type = 2)

BIN
tools/pic/grey.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
tools/pic/red.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
tools/pic/可莉.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tools/pic/可莉2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
tools/pic/可莉3.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
tools/pic/水神1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
tools/pic/灰度测试.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
tools/pic/灰度测试2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
tools/pic/绫华1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
tools/pic/胡桃1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
tools/pic/胡桃2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB