0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

新鲜出炉!LuatOS墨水屏+ESP32C3开发板,自制在线电纸书

合宙LuatOS 2022-07-19 17:53 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

上周合宙发布了一款1.54寸墨水屏开发板,售价仅为16.8元,又掀起一阵抢购热旋风

由于墨水屏的特性,无需背光,在光线照射下观感和纸张印刷效果类似,很适合用来做电纸书。再配合可以使用Wi-Fi的合宙ESP32C3开发板,我们就可以用LuatOS驱动这块墨水屏来做一个在线电纸书了。

5df15bc8-06bf-11ed-9ade-dac502259ad0.gif

- LuatOS在线电纸书 -

接下来,让我们一起看看制作LuatOS在线电纸书的要点吧!

1

基础准备工作


本文电纸书示例主要硬件采用合宙LuatOS墨水屏开发板+ESP32C3开发板,软件建议使用合宙Luatools进行操作。

1.1 合宙LuatOS墨水屏开发板一块:

1.54寸黑白双色墨水屏,分辨率200*200,板载升压电路,仅需正常3.3V供电即可驱动。使⽤LuatOS固件中的eink库,可以⽅便快捷地驱动屏幕。

点击图片链接了解更多:

5e0566d6-06bf-11ed-9ade-dac502259ad0.png

1.2 合宙ESP32C3开发板一块:

目前合宙ESP32C3开发板分为两版:经典款和简约款。须注意的是简约款无串口芯片,从Type-C口连接的话没法用LuatIDE调试,需使用Luatools进行烧录。

点击图片链接了解更多:

5e2b710a-06bf-11ed-9ade-dac502259ad0.png

1.3 接线示意:

合宙LuatOS墨水屏开发板接口兼容合宙LuatOS全系列MCU开发板,对应接口对插即可。

5e6a0dde-06bf-11ed-9ade-dac502259ad0.jpg

2

编写在线电纸书


5e7fef28-06bf-11ed-9ade-dac502259ad0.gif

2.1 解锁GPIO11

由于墨水屏的BUSY引脚连接到了ESP32C3的GPIO11,但是ESP32C3的GPIO11(VDD_SPI)默认功能是给Flash供电,默认情况下无法当作GPIO使用,我们可以使用外部3.3V给Flash供电,把GPIO11释放出来使用。

具体步骤详见【ESP32C3解锁使用IO11】:https://gitee.com/dreamcmi/LuatOS-ESP32/blob/master/doc/VDD_SPI_AS_GPIO.md

2.2 界面及交互设计

使用开发板上的BOOT键(GPIO9)作为功能按键:

单击:下一个
双击:上一个
长按:进入/退出

选择12号中文字体作为显示字体;开机后显示图书列表界面,图书列表一页显示11项,选中的图书高亮显示,长按功能键进入图书阅读界面,阅读界面显示内容为11行*12列,最下面一行显示当前的阅读进度,在阅读界面长按功能键退回到图书列表界面。

2.3 搭建在线电纸书后台服务

后台服务主要提供以下两个HTTP接口供客户端调用:

5fb47a8a-06bf-11ed-9ade-dac502259ad0.png

当前墨水屏使用12号中文字体的情况下,一页显示11行*12列,后台会根据需求分页返回给客户端。

后台源代码详见【电纸书后台源码】:

https://github.com/JeremyHash/EinkBook-LuatOS/blob/master/Server/main.go

2.4 封装必要模块

在线电纸书需要Wi-Fi连接和发起HTTP请求两个基本需求,这部分代码比较多,封装成两个函数:

创建Wi-Fi连接


手机横屏/上下滑动查看完整代码:

function wifiConnect.connect(ssid, passwd)

local waitRes, data

if wlan.init() ~= 0 then

log.error(tag .. ".init", "ERROR")

return false

end

if wlan.setMode(wlan.STATION) ~= 0 then

log.error(tag .. ".setMode", "ERROR")

return false

end

if USE_SMARTCONFIG == true then

if wlan.smartconfig() ~= 0 then

log.error(tag .. ".connect", "ERROR")

return false

end

waitRes, data = sys.waitUntil("WLAN_STA_CONNECTED", 180 * 10000)

log.info("WLAN_STA_CONNECTED", waitRes, data)

if waitRes ~= true then

log.error(tag .. ".wlan ERROR")

return false

end

waitRes, data = sys.waitUntil("IP_READY", 10000)

if waitRes ~= true then

log.error(tag .. ".wlan ERROR")

return false

end

log.info("IP_READY", waitRes, data)

return true

end

if wlan.connect(ssid, passwd) ~= 0 then

log.error(tag .. ".connect", "ERROR")

return false

end

waitRes, data = sys.waitUntil("WLAN_STA_CONNECTED", 10000)

if waitRes ~= true then

log.error(tag .. ".wlan ERROR")

return false

end

log.info("WLAN_STA_CONNECTED", waitRes, data)

waitRes, data = sys.waitUntil("IP_READY", 10000)

if waitRes ~= true then

log.error(tag .. ".wlan ERROR")

return false

end

log.info("IP_READY", waitRes, data)

return true

end

发起HTTP请求


手机横屏/上下滑动查看完整代码:

local methodTable = {

GET = esphttp.GET,

POST = esphttp.POST,

PUT = esphttp.PUT,

DELETE = esphttp.DELETE

}

function httpLib.request(method, url, head)

local responseCode = 0

local httpc = esphttp.init(methodTable[method], url)

if httpc == nil then

esphttp.cleanup(httpc)

return false, responseCode, "create httpClient error"

end

if head ~= nil then

for k, v in pairs(head) do

esphttp.set_header(httpc, k, v)

end

end

local ok, err = esphttp.perform(httpc, true)

if ok then

local response = ""

while 1 do

local result, c, ret, data = sys.waitUntil("ESPHTTP_EVT", 20000)

-- log.info("ESPHTTP_EVT", result, c, ret, data)

if result == false then

esphttp.cleanup(httpc)

return false, responseCode, "wait for http response timeout"

end

if c == httpc then

if esphttp.is_done(httpc, ret) then

esphttp.cleanup(httpc)

return true, esphttp.status_code(httpc), response

end

if ret == esphttp.EVENT_ON_DATA then

response = response .. data

end

end

end

else

esphttp.cleanup(httpc)

return false, responseCode, "perform httpClient error " .. err

end

end

2.5 封装必要模块编写电纸书代码

下面开始电纸书部分的逻辑代码,主要分为初始化FDB、初始化墨水屏、文字函数、图书列表及内容函数、网络及按键功能等几个部分:

初始化FDB


assert(fdb.kvdb_init("env", "onchip_flash") == true, tag .. ".kvdb_init ERROR")

初始化墨水屏


由于我们使用的是ESP32C3,连接的SPI通道id为2,初始化成功之后需要设置墨水屏的大小,然后将墨水屏完整的黑白刷新一次来清除之前显示内容的残留,再设置我们要使用的12号中文字体。

代码如下:

-- 局刷模式

eink.setup(1, 2, 11, 10, 6, 7)

-- 设置分辨率200*200

eink.setWin(200, 200, 0)

-- 全刷一次屏幕防止之前的显示内容残留

eink.clear(0, true)

eink.show(0, 0)

eink.clear(1, true)

eink.show(0, 0)

eink.setFont(eink.font_opposansm12_chinese)

封装墨水屏显示文字函数


function einkShowStr(x, y, str, colored, clear, show)

-- 每20次刷屏就全刷一次防止显示残留

if einkPrintTime > 20 then

einkPrintTime = 0

eink.rect(0, 0, 200, 200, 0, 1)

eink.show(0, 0, true)

eink.rect(0, 0, 200, 200, 1, 1)

eink.show(0, 0, true)

end

if clear == true then

eink.clear()

end

eink.print(x, y, str, colored)

if show == true then

einkPrintTime = einkPrintTime + 1

eink.show(0, 0, true)

end

end

封装渲染图书列表和图书内容函数


手机横屏/上下滑动查看完整代码:

function showBookList(index)

local firstIndex

for k, v in pairs(onlineBooksShowTableTmp[1]) do

firstIndex = v["index"]

end

-- 当要高亮的图书索引超过了当前的列表,扩充当前列表

if index > firstIndex + 10 then

onlineBooksShowTableTmp = getTableSlice(onlineBooksShowTable, index - 10, index)

end

if index < firstIndex then

onlineBooksShowTableTmp = getTableSlice(onlineBooksShowTable, index, index + 10)

end

einkShowStr(0, 16, "图书列表", 0, true)

local ifShow = false

local len = getTableLen(onlineBooksTable)

local showLen = getTableLen(onlineBooksShowTableTmp)

if len == 0 then

einkShowStr(0, 32, "暂无在线图书", 0, false, true)

return

end

local i = 1

for k, v in pairs(onlineBooksShowTableTmp) do

for name, info in pairs(v) do

local bookName = string.split(name, ".")[1]

local bookSize = tonumber(info["size"]) / 1024 / 1024

if i == showLen then

ifShow = true

end

if info["index"] == index then

eink.rect(0, 16 * i, 200, 16 * (i + 1), 0, 1, nil, ifShow)

einkShowStr(0, 16 * (i + 1), bookName .. " " .. string.format("%.2f", bookSize) .. "MB", 1,

nil, ifShow)

else

einkShowStr(0, 16 * (i + 1), bookName .. " " .. string.format("%.2f", bookSize) .. "MB", 0,

nil, ifShow)

end

i = i + 1

end

end

end

function showBook(bookName, bookUrl, page)

sys.taskInit(function()

waitHttpTask = true

for i = 1, 3 do

local result, code, data = httpLib.request("GET", bookUrl .. "/" .. page)

log.info("SHOWBOOK", result, code)

if result == false or code == -1 or code == 0 then

log.error("SHOWBOOK", "获取图书内容失败 ", data)

else

local bookLines = json.decode(data)

for k, v in pairs(bookLines) do

if k == 1 then

einkShowStr(0, 16 * k, v, 0, true, false)

elseif k == #bookLines then

einkShowStr(0, 16 * k, v, 0, false, false)

else

einkShowStr(0, 16 * k, v, 0, false, false)

end

end

-- 最后一行渲染读书进度

einkShowStr(60, 16 * 12 + 2, page .. "/" .. onlineBooksTable[bookName]["pages"], 0, false, true)

break

end

end

waitHttpTask = false

end)

end

连接网络并获取图书列表


需要提前创建几个函数,用来处理下面获取到的图书列表数据。

手机横屏/上下滑动查看完整代码:

-- 获取table的切片并作为一个新的table返回

function getTableSlice(intable, startIndex, endIndex)

local outTable = {}

for i = startIndex, endIndex do

table.insert(outTable, intable[i])

end

return outTable

end

-- 通过#获取table长度不靠谱,所以需要这个函数

function getTableLen(t)

local count = 0

for _, _ in pairs(t) do

count = count + 1

end

return count

end

-- 需要将获取到的图书列表格式化符合我们下面的需求

function formatOnlineBooksTable(inTable)

local outTable = {}

local i = 1

for k, v in pairs(inTable) do

v["index"] = i

table.insert(outTable, {

[k] = v

})

i = i + 1

end

return outTable

end

```

调用之前封装的WiFi连接函数,将获取到的图书列表解析并显示

代码如下:

for i = 1, 5 do

local result, code, data = httpLib.request("GET",serverAdress .. "getBooks")

if result == false or code == -1 or code == 0 then

log.error(tag, "获取图书列表失败 ", data)

if i == 5 then

einkShowStr(0, 16, "连接图书服务器失败 正在重启", 0,true, true)

rtos.reboot()

end

else

onlineBooksTable = json.decode(data)

onlineBooksTableLen = getTableLen(onlineBooksTable)

onlineBooksShowTable = formatOnlineBooksTable(onlineBooksTable)

onlineBooksShowTableTmp = getTableSlic(onlineBooksShowTable, 1, 11)

-- 渲染图书列表,索引从1开始

showBookList(1)

btnSetup(9, 1000, btnShortHandle, btnLongHandle, btnDoublehandle)

break

end

sys.wait(1000)

end

初始化功能键并编写对应的按键处理


初始化BOOT/GPIO9为功能键,在对应的按键事件处理函数中,调用之前封装好的渲染图书列表或渲染图书内容的函数。

手机横屏/上下滑动查看完整代码:

-- 短按处理函数

function btnShortHandle()

-- 如果当前http请求未完成则直接返回

if waitHttpTask == true then

waitDoubleClick = false

return

end

-- 如果当前页面在图书列表,短按会高亮下一本书

if PAGE == "LIST" then

if einkBooksIndex == onlineBooksTableLen then

einkBooksIndex = 1

else

einkBooksIndex = einkBooksIndex + 1

end

showBookList(einkBooksIndex)

-- 如果当前页面在图书阅读页面,短按进入下一页

else

local i = 1

local bookName = nil

for k, v in pairs(onlineBooksTable) do

if i == einkBooksIndex then

bookName = k

end

i = i + 1

end

local thisBookPages = tonumber(onlineBooksTable[bookName]["pages"])

-- 如果当前页已是最后一页,直接返回

if thisBookPages == gpage then

waitDoubleClick = false

return

end

gpage = gpage + 1

-- 渲染下一页

showBook(bookName, serverAdress .. string.urlEncode(bookName), gpage)

log.info(bookName, gpage)

-- 把新的阅读记录存入fdb

fdb.kv_set(bookName, gpage)

end

waitDoubleClick = false

end

-- 长按处理函数

function btnLongHandle()

if waitHttpTask == true then

return

end

-- 如果当前在列表页进入内容阅读页

if PAGE == "LIST" then

PAGE = "BOOK"

local i = 1

local bookName = nil

for k, v in pairs(onlineBooksTable) do

if i == einkBooksIndex then

bookName = k

end

i = i + 1

end

local pageCache = fdb.kv_get(bookName)

log.info(bookName, pageCache)

if pageCache == nil then

gpage = 1

showBook(bookName, serverAdress .. string.urlEncode(bookName), gpage)

else

gpage = pageCache

showBook(bookName, serverAdress .. string.urlEncode(bookName), pageCache)

end

-- 如果当前在内容页进入列表页

elseif PAGE == "BOOK" then

PAGE = "LIST"

showBookList(einkBooksIndex)

end

end

-- 双击处理函数

function btnDoublehandle()

if waitHttpTask == true then

return

end

-- 若果当前在列表页,高亮上一项

if PAGE == "LIST" then

if einkBooksIndex == 1 then

einkBooksIndex = onlineBooksTableLen

else

einkBooksIndex = einkBooksIndex - 1

end

showBookList(einkBooksIndex)

-- 如果当前在内容页,读取上一页内容

else

-- 如果已是第一页直接返回

if gpage == 1 then

return

end

gpage = gpage - 1

local i = 1

local bookName = nil

for k, v in pairs(onlineBooksTable) do

if i == einkBooksIndex then

bookName = k

end

i = i + 1

end

log.info(bookName, gpage)

fdb.kv_set(bookName, gpage)

showBook(bookName, serverAdress .. string.urlEncode(bookName), gpage)

end

end

-- 按键中断处理函数

function btnHandle(val)

if val == 0 then

-- 按下时在等待双击状态时,停止短按事件函数的定时器,并触发双击

if waitDoubleClick == true then

sys.timerStop(gShortCb)

gDoubleCb()

waitDoubleClick = false

return

end

-- 启动一个定时器触发长按处理函数

sys.timerStart(longTimerCb, gPressTime)

gBtnStatus = "PRESSED"

else

-- 停止长按处理函数的定时器

sys.timerStop(longTimerCb)

-- 如果当前状态为短按下,开启一个定时器来触发短按处理函数

if gBtnStatus == "PRESSED" then

sys.timerStart(gShortCb, 500)

waitDoubleClick = true

gBtnStatus = "IDLE"

-- 如果当前状态以为长按处理函数执行完成,改变状态为IDLE并返回

elseif gBtnStatus == "LONGPRESSED" then

gBtnStatus = "IDLE"

end

end

end

-- 初始化按键函数

function btnSetup(gpioNumber, pressTime, shortCb, longCb, doubleCb)

gpio.setup(gpioNumber, btnHandle, gpio.PULLUP)

gPressTime = pressTime

gShortCb = shortCb

gLongCb = longCb

gDoubleCb = doubleCb

end

-- 初始化GPIO9为上拉中断模式,并注册短按/长按/双击的处理函数

btnSetup(9, 1000, btnShortHandle, btnLongHandle, btnDoublehandle)

2.6相关资料链接

完整项目代码:

https://github.com/JeremyHash/EinkBook-LuatOS

烧录教程:

https://wiki.luatos.com/boardGuide/flash.html

墨水屏开发板资料:
https://wiki.luatos.com/peripherals/eink_1.54/index.html

ESP32C3开发板资料:
https://wiki.luatos.com/chips/esp32c3/index.html

好了,今天就分享到这里
以上内容你学会了吗
自己制作一款电纸书愉快玩耍吧

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 电纸书
    +关注

    关注

    0

    文章

    26

    浏览量

    9861
  • 开发板
    +关注

    关注

    25

    文章

    6146

    浏览量

    113844
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    同样是乐鑫科技ESP32-P4C5开发板,到底应该怎么选?选型指南在这!

    更多都是用乐鑫科技ESP32-P4和ESP32-C5芯片设计的开发板,启明云端为什么要设计两款?WT99P4C5-S1和WTDKP4C5-S
    的头像 发表于 12-15 18:03 842次阅读
    同样是乐鑫科技<b class='flag-5'>ESP32-P4C</b>5<b class='flag-5'>开发板</b>,到底应该怎么选?选型指南在这!

    ESP32-P4全功能开发板ESP32-P4-TINY开发板该怎么选?看这篇就够了!

    启明云端基于乐鑫科技ESP32-P4芯片设计了多款开发板,这些开发板有什么区别?基于应用场景如何选择?本期,我们聚焦两款代表性产品:WT99P4C5-S1
    的头像 发表于 12-09 18:02 167次阅读
    <b class='flag-5'>ESP32</b>-P4全功能<b class='flag-5'>开发板</b>和<b class='flag-5'>ESP32</b>-P4-TINY<b class='flag-5'>开发板</b>该怎么选?看这篇就够了!

    乐鑫科技ESP32-S3开发板+超声波雾化,给智能鱼缸整点美学氛围

    我们为什么要给智能鱼缸弄上雾化?美学氛围当然是一方面,但更重要的功能是局部加湿,为水面植物与周围空气提供柔和湿度。因此我们特意推出本期内容:用乐鑫科技ESP32-S3开发板和超声波雾化模块让水面形成
    的头像 发表于 12-08 18:04 135次阅读
    乐鑫科技<b class='flag-5'>ESP32-S3</b><b class='flag-5'>开发板</b>+超声波雾化,给智能鱼缸整点美学氛围

    这块乐鑫科技ESP32-C3开发板太懂开发者了!双无线+全接口,不要太实用

    做智能家居项目时,开发板接口不够用?调试工业传感器时,无线连接总是不稳定?想快速验证创意,却被复杂的烧录流程耽误半天时间?不要慌!真正懂开发者的物联网开发板来了!WT9901C3-SN
    的头像 发表于 12-01 18:02 417次阅读
    这块乐鑫科技<b class='flag-5'>ESP32-C3</b><b class='flag-5'>开发板</b>太懂<b class='flag-5'>开发</b>者了!双无线+全接口,不要太实用

    低成本开源!用乐鑫科技ESP32-S3开发板轻松驱动无刷电机,保姆级教程来了!

    想用ESP32-S3开发板驱动无刷电机却不知从何下手?本教程将手把手教你完成从硬件连接到软件编程的全流程,无论你是新手还是有一定经验的开发者,都能轻松掌握!本教程代码已全部开源!后台私信关键词
    的头像 发表于 11-06 18:03 304次阅读
    低成本开源!用乐鑫科技<b class='flag-5'>ESP32-S3</b><b class='flag-5'>开发板</b>轻松驱动无刷电机,保姆级教程来了!

    低成本开源!手把手教你用乐鑫科技ESP32-P4开发板制作电脑监测

    ESP32-P4-TINY开发板自制一个电脑性能监控,让系统状态一目了然!代码全部开源!后台私信关键词P4TINY性能监测副自动获取
    的头像 发表于 11-04 18:05 304次阅读
    低成本开源!手把手教你用乐鑫科技<b class='flag-5'>ESP32</b>-P4<b class='flag-5'>开发板</b>制作电脑监测<b class='flag-5'>屏</b>!

    乐鑫科技ESP32-S3开发板配单色LED,竟能玩出这么多花样!代码开源,速来白嫖!

    的效果。材料准备1×ESP32-S3开发板1×USB转TypeC线1×165x55x10mm面包若干15cm杜邦线若干5mm单色LED本教程ESP32-S3
    的头像 发表于 10-23 18:02 1765次阅读
    乐鑫科技<b class='flag-5'>ESP32-S3</b><b class='flag-5'>开发板</b>配单色LED,竟能玩出这么多花样!代码开源,速来白嫖!

    ESP32-P4 口袋开发板 启明云端 WT9932P4-TINY开发板

    在万物互联的智能时代,您是否还在为寻找一款性能强大、接口丰富、应用灵活的嵌入式开发板而烦恼?启明云端全新推出的WT9932P4-TINY开发板,基于乐鑫科技高性能ESP32-P4芯片匠心打造,专为安
    的头像 发表于 09-11 18:06 1030次阅读
    <b class='flag-5'>ESP32</b>-P4 口袋<b class='flag-5'>开发板</b> 启明云端 WT9932P4-TINY<b class='flag-5'>开发板</b>

    ESP32-P4-MINI开发板开箱和上手指南来了!速速码住!

    上期“梦中情ESP32-P4-MINI开发板一出就备受青睐这期我们立马就端着开箱和上手指南来了!不用惊叹,我们就是这么迅速,请把“启明云端权威”打在公上好嘛!开箱展示拿到
    的头像 发表于 07-25 18:02 1468次阅读
    <b class='flag-5'>ESP32</b>-P4-MINI<b class='flag-5'>开发板</b>开箱和上手指南来了!速速码住!

    ESP32开发板元件资料

    ESP32开发板元件
    发表于 07-21 14:47 14次下载

    ESP32-P4 C5开发板烧录小智全流程!速看!

    没错,你没有看错!我们带着WT99P4C5-S1开发板烧录小智全流程走来了!开发板搭载乐鑫科技ESP32-P4和ESP32-C5芯片,代码完
    的头像 发表于 07-04 18:03 1689次阅读
    <b class='flag-5'>ESP32</b>-P4 <b class='flag-5'>C</b>5<b class='flag-5'>开发板</b>烧录小智全流程!速看!

    ESP32-S3开发板烧录小智AI系统全流程指南

    在AI语音交互领域不断发展的今天,开发者们对于功能强大、开源灵活的开发板需求日益增长。今天,我们就来详细了解一下ESP32AgentDevKit烧录小智的全流程,这款搭载乐鑫科技ESP32-S
    的头像 发表于 06-16 18:01 7084次阅读
    <b class='flag-5'>ESP32-S3</b><b class='flag-5'>开发板</b>烧录小智AI系统全流程指南

    ESP32-C3开发板全面支持小智AI!烧录实战指南来了!

    本文将详细解析基于乐鑫ESP32-C3芯片的ZXAIEC43开发板烧录“小智”AI语音系统的全流程。该方案代码完全开源,支持深度定制开发,适用于智能玩具、潮玩手办及智能家居控制等多元场景。开发
    的头像 发表于 06-13 18:01 3662次阅读
    <b class='flag-5'>ESP32-C3</b><b class='flag-5'>开发板</b>全面支持小智AI!烧录实战指南来了!

    基于ESP32C3的智能小车设计

    你有没有想过,从零开始亲手制作一辆坚固耐用的遥控越野车?今天,小编就带你走进一个融合机械、电子与物联网的精彩DIY项目——一款由 Seeed Studio XIAO ESP32C3 强力驱动的 3D打印4x4 RC漫游车!
    的头像 发表于 06-04 11:11 1784次阅读
    基于<b class='flag-5'>ESP32C3</b>的智能小车设计

    用AI人脸识别开发板BW21-CBV-Kit驱动墨水

    试着用BW21-CBV-Kit点亮2.9寸墨水开发板例程非常丰富,在arduino上开发的话上手超级容易。
    的头像 发表于 03-04 18:24 1564次阅读
    用AI人脸识别<b class='flag-5'>开发板</b>BW21-CBV-Kit驱动<b class='flag-5'>墨水</b><b class='flag-5'>屏</b>