• 欢迎大家分享资料!前往留言板评论即可!

菜鸟学写Cat1 Demo集(连载)

合宙 模组资料网 5个月前 (05-15) 62次浏览 已收录 0个评论 扫描二维码
文章目录[隐藏]

@[TOC]

菜鸟学写Cat1 Demo集(五十七) FairWell

FairWell

今天继续研究腾讯云。 发现前面握手一直没能成功,有几个原因。

1) 固件不对,Luat_V0022_RDA8910_TTS_FLOAT,并不支持Hash256的加密算法,需要更换成不支持TTS的Luat_V0022_RDA8910_FLOAT固件。

2) 由于腾讯云持续的升级,导致iRTU1.9.3的源码不能直接运行。 需要经过修改才行。

综上,今天连接腾讯云的努力也就彻底失败了。 但这确实是一件好事,让我意识到了自己真实水平的巨大差距。 像腾讯云iot这个小门槛,一般年轻人大概突击一晚上就能搞定,但我花了足足三天时间,也没有搞出来。 如果打工,就这效率在职场上直接就被毙了。

每天在doc.openluat.com 写写文字,确实挺痛快的,但是我发现最近八卦猛增,核心板的销量却几乎停滞,也就是说,我这样的做法并没有迎合到真实客人的需求。 卖板子才能搞到钱,但写日记并不能。 所以每天写八卦这个事儿我打算先停下来了,直到我找到正确的方向。 Midemo还有没有继续开发的必要,我还没想好。 这个项目开始快两个月了,目前只有7颗星星3个folk用户,我观察下近期有没有起色,再决定。

感谢合宙提供了这么好的平台,感谢朋友们的陪伴和留言,感谢一直支持的小伙伴们。 现在经济形势非常不好,生存是第一要务,希望您能理解,支持,再会。

最后期望大家多多买合宙模块,特别是Air724,这模块必火,立帖为证。

菜鸟学写Cat1 Demo集(五十六) 密码工程师

八卦

今日只有八卦,没正文。

今天继续花了大把的时间研究腾讯iot云,稍微有了一点点起色,但是还是没有调通,报错是鉴权失败…其实如果认真读一下iRTU关于腾讯云部分的源码,会发现这个认证机制并不麻烦, 按照一个特定http头发送一个请求,使用TC3-HMAC-SHA256算法,这是腾讯自己做的一个SHA256的算法去换另一个带期限的MQTT的临时用户名密码,然后发起MQTT连接建立通讯。

但就是这么简单的一个流程,被腾讯云服务拆解的支离破碎。 增加了用户权限分配、API版本控制等等技术细节。 毫不夸张的讲,明明我们只想去街边买两根青菜,他非要让我们买高铁票去沿着国境线来个自助游然后再回到家门口…

这让我想起了当年在Moto工作时候的一个段子。 我们搞电子开发的工程师都管IT叫保密工程师,我那时还不太懂,现在好像懂一点点了。 就是我不告诉你咋做的,给你个黑盒子,也不给你文档,急死你你也用不起来。 现在其实不光腾讯这么搞, 基本上BAT+电信都这么来。 依仗着自己拥有自己相应的资源就硬让用户去大笔时间去学所谓的平台技术,文档支离破碎,可读性很差,往往需要花大把时间猜谜语一样搞懂他们的流程。

这样的做法,未来一定会增加平台的粘性,也是保护自家商业利益的一种手法。 但作为一个普通用户到底还要不要继续用他家技术,我大概要想一想。 明天最多再花一天时间,不行我就放弃了。 确实能力不足,不行的事,我只能放弃。

菜鸟学写Cat1 Demo集(五十五) 腾讯云入门 2020-10-17

八卦

人上了岁数,就容易腰酸背痛。 昨天晚上贪玩捣鼓电器,结果弄到三点多才睡。今天整个人都迷迷糊糊的,别说工作,走路都走不出直线。 人这一生讲真不容易。 岁数小的时候没自由,长大了没对象,岁数大了成家立业了,但身子骨又不行了。 反正真正的好时候着实不多。

所以比较好的一个方法是及时行乐,好好活在当下。 借用《黑冰》里男主角郭小鹏的一句话”是人就有问题”,人啥岁数都有啥岁数的愁可发。 考虑到这,不如把当下喜欢的事儿做好,这样也算不负韶华…后半句我忘记了

比如当下我喜欢写lua代码。 过去我用过C,C++ C++++,其余的一些语言也试过,比如js,delphi,但我必须承认lua的语法是我用过里最简洁的。 不要跟我说python好,php是最好的语言之类的话。 很多人是看中了这些语言生态所附带的东西,我讲的就说语言本身,lua算得上白富美之一。 我有理由认为,lua的美体现在简洁,一致,这个你用一段时间就能体会到了,我有理由相信,未来会有他的一片属于他自己的领域,比如可以是物联网脚本。

腾讯IOT云入门

现在外面的各种云是琳琅满目,大概几个月前,我研究过两天阿里云。 并不太难上手。 前几天有朋友提到腾讯云,今天就开始碰。 算是开个头吧。 使用腾讯家的服务,我个人是有小九九的,希望可以直接用小程序控制腾讯IOT云,都是一家的产品,这种几率是很大的,但真实情况还是得实测。

腾讯云第一印象给我的还不错,至少网页加载速度远远优于阿里的IOT方案,不过类似于Onenet,腾讯云也搞了两套独立的平台,有点让人无所适从: https://console.cloud.tencent.com/iothub 登录控制台后,搜索IOT关键字可以看到两个平台。一个叫“物联网通信”,另一个叫“物联网开发平台”,在头部公司这种情况并不鲜见,可能是两个互相竞争的独立团队在搞,目前来看,似乎名字长的这个功能做的相对完备一些,至少内部长得和阿里云更像一点。 那个短的加载速度很快,功能相对更简单。

demo内置了iRTU固件,我今天尝试使用独立的iRTU固件,反复试了几个配置,都没有连接成功。 今天很头大。希望明天能好运些吧。

菜鸟学写Cat1 Demo集(五十四) 按钮模块 2020-10-16

八卦

今天继续的恢复体力,特别有个哥们的一番话鼓励了我继续写下去… 我们暂时加这个哥们Y君吧。

Y君今天给我发QQ消息说,他辞职了,我本来想说你这样做冲动了。 后来又一想,脚舒不舒服鞋子最舒服这个道理,我就把话收回来了。 因为当事者肯定比我掌握的咨询要更全面,人家不会拿自己的前程开玩笑。 退一步说,就算决策真的是错的,至少可以获得经验,老是强把拐杖塞在人家手里不让人学走路还说是帮人这不就成了坑人了么。 所以,我就不咸不淡的说了几句祝福的话。 后来这个哥们告诉我,每天看我写的八卦,结果帮他挣钱了,而且还挣了好几万。 我这就听傻了,我要是真有挣钱的路我怎么会写出来,自己还不去发财。 于是我就问这个小哥到底是怎么回事。

他是这么说的,他家境还不错,家里给了一笔钱,大概有小几百万,本来想创业的,后来看到我创业的结果这么惨,一想就去买房了,结果买的房子已经升值了十万了……….

此处我真得为自己开脱开脱,不然以后跳进黄河也洗不清。 本连载属于娱乐性文章,绝不构成任何形式的人生建议。 您的人生您做主。 我这人变得比天上的云彩还快,今天觉得苦,明天可能分分钟就美得不行。 话说现在社会变化太快,今天台上唱的好听的,不一定明天还能继续登台。 今天是阶下囚的,明天可能摇身一变就成了亿万富翁。 人生这东西谁猜得准? 至少我是猜不准,不然我也不会混成现在这样。

您就把我当一个乐,看看得了,千万别当真。 如果您读了文章以后发财了,那是您自己的本事,我最多是抛砖引玉启发到了您。 如果倒了霉,那也是您自己运气不佳,我只能跟您说声对不起。 找我赔钱我是一毛也给不起的。

按键模块

今天咬咬牙,把按键模块写完了。 功能挺简单,就是把任意一个或者多个GPIO配置成按钮模式。 在对应的GPIO引脚和1.8V之间接一个开关就能用了。 按键分为长摁和短按。 如果按键时间短于1.5秒,就算短摁,否则就算长按。 短按的触发时机是松开按键时,长按的触发时间是时间达到1.5S后,无论是否松开都会触发。

使用方法是先配置bs.lua

--按钮功能,需要使用空白的GPIO口
BTNPins={9,10,11,12}

把GPIO添加上就算完事,前提是GPIO一定是空闲的。 比如说GPIO9,10,11,12正好对应SPI,所以您用了按钮功能,所有使用SPI端口的功能就不能开了,像WS2812B,步进伺服电机都用不了,就酱紫。

代码贴上,挺好懂得。 稍微说一个新的知识点,我也是才突然悟出来的。 所以写下来,记录下。

在lua里函数也可以看成是变量,您可以像定义变量的方式一样定义一个函数,当然也可以是数组,定义方式如下

foo=function()

函数的内容

end

如何是函数数组,可以这样写

--定义函数数组
foo={}

foo[i]=function()

函数的内容,i可以拿来用的

end

不太难懂吧,但我今天才弄懂,真是笨死了

下面是源码,我不建议您看,太麻烦。 就直接拿来用就行了。 这是使用效果,GPIO9,10,11,12对应4个按钮。我长按和短按了按钮9和11,顺手我也把前面写的一键打电话功能的代码改了,现在也是长摁不用松手就可以出发长按事件了,似乎这样更反人性一些。

菜鸟学写Cat1 Demo集(连载)

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM(Apache2)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:按钮按键模块
-- @author miuser
-- @module midemo.btn
-- @license MIT
-- @copyright [email protected]luat
-- @release 2020-10-16
--------------------------------------------------------------------------
-- @使用方法
-- @按钮连接到对应的IO口和1.8V之间
-- @当短按按钮时,将向串口发送BUTTON_SHORT_PRESSED,XX 消息
-- @当长按按钮超过1.5S时,将向串口发送BUTTON_LONG_PRESSED,XX 消息
-- @同时系统还会发送消息 BTN_SHORT_PRESSED 和 BTN_LONG_PRESSED 的消息,消息有一个参数是按钮的GPIO号


require"pins"
require"utils"
require"pm"
require"common"

module(...,package.seeall)


--上一次的电平状态表,以管脚号为索引
 local pinLastStage={}
--获取GPIO状态的函数表,以管脚号为索引
local getGpioFnc={}
--当收到GPIO输入测试的时候执行回调函数

--通过消息发送调试信息到串口模块
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end
function gpioIntFnc(msg)
    local trigerPin=""
    local response=""
    --检测哪个IO口发生电平变化
    for i, v in ipairs(bs.BTNPins) do
        if getGpioFnc[v]()~=pinLastStage[v] then
            trigerPin=v
            pinLastStage[v]=getGpioFnc[v]()
        end
    end
    if (trigerPin=="") then return end
    local level=getGpioFnc[trigerPin]()
    --上升沿中断
    if msg==cpu.INT_GPIO_POSEDGE then
        sys.publish("GPIO_LEVEL_CHANGE",trigerPin,0)
    else
     --下降沿中断       
        sys.publish("GPIO_LEVEL_CHANGE",trigerPin,1)
    end
end

for i=1,#bs.BTNPins do
    pinLastStage[bs.BTNPins[i]]=0
end

for i=1,#bs.BTNPins do
    --设置中断函数和电平检测函数
    getGpioFnc[bs.BTNPins[i]]=pins.setup(bs.BTNPins[i],gpioIntFnc)
    --引脚均设为下拉
    pio.pin.setpull(pio.PULLDOWN,bs.BTNPins[i])

end

---按钮事件
sta,longcb,shortcb,longtimercb={},{},{},{}

for i=1,#bs.BTNPins do
    sta[i]= "IDLE"
    longtimercb[bs.BTNPins[i]]=function() 
        --log.info("midemo.btn","pin.longtimercb "..bs.BTNPins[i])
        sta[bs.BTNPins[i]] = "LONGPRESSED"
        longcb[bs.BTNPins[i]]()
    end
    shortcb[bs.BTNPins[i]]=function()
        log.info("midemo.btn","pin.shortpress "..bs.BTNPins[i])
        write("BUTTON_SHORT_PRESSED,"..bs.BTNPins[i])
        sys.publish("BTN_SHORT_PRESSED",bs.BTNPins[i])
    end
    longcb[bs.BTNPins[i]]=function()
        log.info("midemo.btn","pin.longpress "..bs.BTNPins[i])
        write("BUTTON_LONG_PRESSED,"..bs.BTNPins[i])
        sys.publish("BTN_LONG_PRESSED",bs.BTNPins[i])
    end
end

--响应对应pin的按钮,edge=0 上升沿, edge=1 下降沿
local function keyMsg(pin,edge)
    if edge==0 then
        sta[pin] = "PRESSED"
        sys.timerStart(longtimercb[pin],1500)
    else
        sys.timerStop(longtimercb[pin])
        if sta[pin]=="PRESSED" then
            if shortcb[pin] then shortcb[pin]() end
        end
        sta[pin] = "IDLE"
    end
end
sys.subscribe("GPIO_LEVEL_CHANGE", keyMsg) 

菜鸟学写Cat1 Demo集(五十三) 说说啥叫生态 2020-10-15

八卦

今天状态不行,腰子疼得厉害。 手不停的发抖,虽然也出去散步运动,尝试食疗,都没啥效果。 所以今天工作的产出为零,没办法,只能耍耍贫嘴,也许比交白卷好一些吧…也许

如果说两年前创业最流行的词汇是平台,那今年最流行的词汇恐怕要属生态了。 啥叫生态,这个我不太懂,就去问百度叔叔,他的回答是,生态指“一切生物的生存状态”,就叫生态。 那这么说,生态肯定得是生物才有,我就接着琢磨,你看一颗大树肯定是活的,那应该叫生态,阿猫阿狗肯定更算, 那一直草履虫算不算? 肯定也得算。 如果说生物,这我就好理解多了。 那就是生老病死这一套活儿,对吧,凡是喘气的都离不开这些。

就说人吧,人为啥会出生,那肯定是父母喜欢生养,不喜欢就不会生孩子,也就不会有孩子出生。 孩子出生以后呢,肯定是自己想活着,才能活下去,否则那叫自杀,这个属于小概率事件几十万分之一的概率,我们忽略它。 这我说的是原因。 下面再说组成,人肯定是胳膊腿加脑袋身子这么一个结构,但是你随便弄几个胳膊腿身子脑袋拼在一块,肯定也不行对吧,还得长在一块才行。 所以说内在的连接很重要,而且这玩意儿光连上还不够,还得永不停歇的交换物资资源。 打个比方,人憋过气不出两分钟人就凉了,你再救也就不活了。

如果我们假想生态企业也是一个生态,那大概应该也是这么个意思。 首先说,你必须得有强烈的生存意志,然后呢,企业的各个组成部分必须得协作进行资源交换,最后呢还得持续的获得营养,这才能算生态企业,更进一步说,企业的生存一刻都不能中断,不喘气大半年,基本就不可能救回来了。

如果我的猜想是对的,那投一些钱租个店面,招几个按规章制度办事的雇员,对外营业这真心不能算生态。 一来因为这样的买卖姥姥不疼舅舅不爱,二来,参与者之间都没啥个人连接,资源交流意愿也不强,那其实并没有什么生存可讲。 但并不等于他不挣钱..

你比如说,制氧机,不用光合作用也能产出氧气,小爱就算不喘气也能和你聊天。 机器其实很大程度上也能替代生态。 设想一下,几千几万人,每个人按照既定的规则,既定的逻辑做事情,形成一个流水线似的大工厂,各司其职,分工合作,生产效率也能杠杠的,出产的产品也可以是优质的。 听说最近西方又再搞人造肉,让人可以彻底告别茹毛饮血的时代。

在商业社会,比拼的主要是生产效率,对于出产的产品是机器做的还是人工做的,大家其实不是太关心。 顾客主要是看的还是性价比。 到底是机器产出效率高,还是生态的产出率高确实是一个问题。 想想看,一个人驾驶着超级机器,能产生极大地生产率还是很牛逼的!这好像是钢铁侠吧…

中国没有钢铁侠,中国只有孙悟空,前者靠机器,后者靠法术。 前者演的像未来过些年就能实现,后者是讲了几百年的民间故事。 反正我都听爱看的,虽然明知都是假的!

WS2812b灯板

写到这,我突然意识到一个问题,今天并不是啥都没做,今天有位好心的网友帮我画了块板儿,虽然不是我做的,这也不妨碍我厚脸皮说成是我教唆他帮我做的对吧。 贴上吧。 过几天电路调好了,看看能不能开源一下,这个我得先征求人家的意见,毕竟,产权是人家的不是我的。姓名也先不公开,我征求下意见先。

画的东西,就是前几天midemo的8×8的ws2812b的那个灯板,他做了一个小号的,就酱紫:

菜鸟学写Cat1 Demo集(连载)

有没有发现我无耻的境界上升了一个层次。从白嫖代码上升到了白嫖电路图了…

菜鸟学写Cat1 Demo集(五十二) 性能监测代码 2020-10-14

八卦

今天没有八卦,研究了一天的性能检测代码,最后写出来了,但是挺凑合的。反正我对自己要求也不太高。

性能监测代码

首先先说说我个人使用Air724的一个小体会。 CPU程序运行失常,大多数的原因可以归结成为

1)程序跑飞了

2)硬件资源不够用

前者是可以通过抓log比较容易抓到的,因为有报错的嘛。 后者就比较难,因为只要内存溢出一般就是直接重启。 我写的这个小模块,主要是为了使后者的问题直观化,简单化。

先说说为哈资源不够用了,会导致重启。 很多人其实都写过单片机程序,一定不陌生各种硬件总线, 在给总线收发数据的时候,一般都有一个缓冲器,但是这个缓冲器容量往往就几个字节,如果你的中断处理程序没有及时的把总线送来的数据处理完,就会导致缓冲区溢出,直接重启CPU。 发送也一样,如果用来存数据的数组没有及时把数据从网络送走也会导致内存溢出。 所以CPU占用过高,往往会导致模块异常重启。

内存也一样,我目前的感觉Air724可用内存大概为1M字节,其中还相当一部分被luatask的底层所占用,我们能用的内存大概只有600K的样子。 这个内存的占用包括加载脚本,包括生成的各种变量,各种函数,总之你干啥都要用内存。 内存如果用光了,也是会导致模块直接重启。 总之CPU和内存都很重要,这是一句非常没意义的大实话,我就絮叨一下。

检查的方法,是受群里Wendal和各位群友的指导启发自己发挥瞎搞的,用的方法对不对,我也不清楚。 反正好想凑合能用,总比没有好。 这东西如果要侧准一点,还是得需要底层有API支持,我不懂底层,所以只能是抛砖引玉,先弄一个凑合用,等合宙官方的的人好心再做个更好的吧。

下面是代码,您愿意看就随便看看

--系统资源检测
sys.timerLoopStart(function()
    collectgarbage("collect")
    local used=math.floor(_G.collectgarbage("count"))
    local free=1000-used
    local usedpercent=used/10
    if (free<0) then 
        free=0
        usedpercent=100 
    end
    --log.info("RAM可用空间:", free.."KB")-- 打印占用的RAM
    --log.info("RAM占用率:", usedpercent.."%")-- 打印占用的RAM
    RamUsage=usedpercent
    sys.publish("RAM_USAGE",RamUsage)
end, 1000)

sys.taskInit(function()
    while true do
        tick1=rtos.tick()
        for i=1,10 do
            sys.wait(100)
        end 
        tick2=rtos.tick()
        elapsed=tick2-tick1
        local usedpercent=math.floor(elapsed-200)/2
        if usedpercent>100 then usedpercent=100 end
        --log.info("CPU占用率:",usedpercent.."%")-- 打印CPU占用率
        CpuUsage=usedpercent
        sys.publish("CPU_USAGE",CpuUsage)
        -- sys.wait(100)       
    end
end)

这部分代码,我合并到了OLED模块里,这样可以减少文件数量,目前好像文件数增长过快,很多功能需要合并下了。 目标是尽量让单一文件可以使用,与其他模块尽量没有耦合,减少多个文件构成的模块包数量

显示效果就这样,挺凑合的吧

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(五十一) 性能监测 2020-10-14

八卦

能把系统跑起来是一码事,能让系统稳定的跑起来完全是另一码事,这就好像是会写字,和会写书法一样的道理。 所以经常看到有人会写一点代码,就觉得写代码没啥难的。 会画一点图纸就觉得画图没啥难的了。 其实想生产出一款可以量产的电路绝非易事,同样能做出来一款稳定使用的固件更不可能是学两天半就能搞定的。

也许是我太笨了,经过了两年多的努力,我大概现在大概刚刚可以凑合的画点合宙模块的简单电路,勉强的写一点能用的简单demo代码。 而且我对自己取得的一点进步常常沾沾自喜。

我想说啥,自打用上小米的手机,我发现其实不是差不多,应该说其实是有天壤之别的。比如我现在用的这个手机,打开淘宝占用时间达到了令人发指的1分钟左右,且经常的停止响应,动不动就挂。 但六年前的iphone6也不会卡到哪里。 十几秒就能打开。

真希望以后国人出的产品能多有一些设计精良,余量充足的产品。 起码一款产品能用十年八年的那种。

性能监测

今天工作内容比较无聊,通过LLCOM串口助手反复的测试重负荷情况下的模块稳定性,通过调优,发现其实还可以。

今天的体力非常不好,就汤泡饭了,不详细讲了,总体而言就是想做一个CPU和内存占用的检测器。 但是代码还一点头绪都没有,就先贴上来思路,和大家一起讨论吧。 因为重负荷下,底层的缓冲区将溢出,导致模块死机重启,同样内存来不及进行回收也会导致模块重启,因此我们希望代码运行时,CPU和内存都不要过载,就需要进行监测。

--- sysmonitor
-- @module sysmonitor
-- @author miuser
-- @license MIT
-- @copyright [email protected]luat
-- @release 2020-10-13

sys.timerLoopStart(function()
    log.info("打印占用的内存:", _G.collectgarbage("count"))-- 打印占用的RAM
    log.info("打印可用的空间", rtos.get_fs_free_size())-- 打印剩余FALSH,单位Byte
end, 1000)

sys.taskInit(function()
    while true do
        tick1=rtos.tick()
        for i=1,100 do
            sys.wait(10)
        end 
        tick2=rtos.tick()
        elapsed=tick2-tick1

        log.info("midemo.sysmonitor","系统一秒内运行的tick数为"..elapsed)-- 打印剩余FALSH,单位Byte
        -- sys.wait(100)       
    end
end)

如果您会这方面的知识,麻烦教教我吧,感激不尽。 我仅仅靠自己肯定是搞不定的。

菜鸟学写Cat1 Demo集(五十) Hyperstepper模块 2020-10-12

八卦

我吃饱的时候就喜欢到处转转,特别喜欢到海河边上散步。 天津这个地方自古就是码头文化,三教九流都扎到一块就成了天津。 所以天津这地儿,嘛样的人都有,嘛样的地方都有。 海河也一样,其实海河是多态继承了上游的很多水系,比如最著名的京杭大运河就是海河的一个重要上游支干。 我就琢磨,要是用cat1模块做一个水力发电的遥控潜水艇,突突突突突突的开到杭州偷拍河边搞对象的男女应该挺有意思的。

菜鸟学写Cat1 Demo集(连载)

其实从技术上是可行的。我说说您听听: 首先先做一个大小合适的潜艇,电池蓄电。 当电量消耗光的时候,就找河岸底部一个可以下锚的地方把潜艇拴在河底下,靠水的自然流动就可以驱动摩电滚给电池充电了,这样开俩小时,充一天,妥妥的。 等我发财了就偷偷搞这个!

概念自充电潜水摄像艇示意图

菜鸟学写Cat1 Demo集(连载)

Hyperstepper 模块

据说精密机械一直是中国的一块短板,超高精度的滑台、丝杠、伺服电机基本上都是日本德国这些国家垄断,甚至捷克这种小国家的精密机械都能糟蹋中国国内的友商好几次,一百多岁的老机床PK我们现在的新品并不是电影里的桥段而是真真切切的在发生的。 如果你不是机械这行的人,没人跟你说这事儿而已。

既然是高精尖,肯定跟我扯不上啥关系。 我都是搞低矮搓的那种。 其实很多高科技产品,特别是高精密产品在原理上并不复杂,也没啥门槛。 关键是工艺复杂。 所以这就给了我可乘之机,我把复杂的工艺那块先不去管,先搞搞简单的原理还是可以的。 我说的步进伺服电机就是这样的一种产品。 具体是啥,我不说,您自己去百度。 反正我觉得挺好的。 关键是有这方面的大拿竟然搞开源技术,这才是我学习的动力。而且人家搞的可是可以量产的那种。 我就在淘宝上看到好几个正式的电机厂用的方案都酷似他家的,我有理由怀疑这些技术和这个开源项目有千丝万缕的联系。

好了不卖关子了,这个开源项目叫hyperstepper,您淘宝搜一下就可以了。 他们老板姓杨,我也仅仅知道这么一点。 步进伺服电机其实我也不懂,反正他家搞的很专业这我知道,我关心的主要还是怎么能跟合宙的模块搭上关系。 最好还能直接跟Air724电话核心板连上。 结果随便一连还就真成了,美的我屁颠屁颠。

好了不多废话,直接上代码

这电机就长这样,我这手机卡的要死,拍照太费劲,就不拍了,直接用淘宝上扣的图,希望杨总大人大量不要告我侵权。

菜鸟学写Cat1 Demo集(连载)

一共有六跟连线,这是连接方法

-- VCC,电源,+5~24V, 红
-- GND,地线,GND,黑
-- STEP,脉冲信号,MOSI,白 
-- DIR,方向,P18,黄
-- ENA,使能,P19,橙
-- COM,VIO,VLCD,绿

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

使用就用手机或者串口调试助手发命令就可以了,指令就一条

–正转10度

MOTOROLL,10

–倒转一圈

MOTOROLL,-360

这都看懂了吧。

由于需要高速的脉冲信号,我用的SPI总线,代码比较麻烦,我搞了大概20个小时,一如既往地即写既忘。 您愿意看就直接去gitee上下载吧,源文件名字是 “hyperstepper.lua” 如果您要是愿意给个star,那我就更体面了。

菜鸟学写Cat1 Demo集(四十九) 今日只有八卦 2020-10-11

八卦

昨天内容写的不好,有小群里的朋友提意见说,看起来显得比较消极。 所以今天的内容就得改改,说积极点。 今天其实一直也没闲着,手头前一段有钱的时候买过一大堆的,基本都是各种开源模块,其中有一个模块比较好玩,一直想玩,但没机会。 Hyperstepper 开源伺服步进电机。 这个东东是一个叫杨工的人搞的,具体他叫杨什么不清楚。 下面我简单的介绍一下,好为后面做铺垫

这个开源项目的目标比较明确,设计一款稳定可靠,性能上佳的伺服步进电机。 这种产品是一种界于步进电机和伺服电机之间的产品,功能上和伺服电机类似,具有精确的速度和角度控制,且可以实现诸如柔性控制,这类比较适合机械臂领域使用的功能。 但结构上还是源于廉价的步进电机,只是一个驱动电路的改进,相当于增加了一个磁力传感器,因此这样的电机成本和传统的伺服电机控制器相当,但性能大大优化。

这哥们我关注他很久了,就不紧不慢的做,非常专注,从最早的一个人,零碎的卖,到现在基于他的这个技术的产品淘宝遍地开花其实也只用了三四年的时间。 所以说,其实人只要踏实下心来,专注于一件事,没有不成功的。

大多数不成功的人还是缺乏定力。 那既然懂得这些道理,我就好好和他学习吧。希望向他一样幸运,也做出一个属于自己的生态。

今天的内容是不是阳光了一些呢?

下面附上一张照片,让大家先看看搞的是啥,明天再上具体的代码

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(四十八) WS2812b模块 2020-10-10

八卦

从今天起开始全情加入android阵营,话说过去这些年,我差不多一直使用iphone,主要原因是看重iphone的安全性。 因为我知道即使是apple原厂的工程师从技术上也无法反向破解手机的内容,作为一个曾经认为自己是“高净值”的人士,应该对手机保密有加,怕万一手机里面一些不该让别人看到的东西让别人看到了。 不过现在就没有这个问题了,因为净值基本归零了,也没啥隐私了。 万一有人真是捡到我手机,看了里面心酸的内容,没准会把手机奉还,顺便施舍几块钱这也难说。 所以就不考虑安全问题了,以后主要就只用android手机了。

最主要的原因还是我的iphoneXR没了…,只剩下原来一个花了899卖的小米的二奶机,只好就用他吧。 以后也不打算再买新的iphone了,主要是太贵了,买不起。 其实说真话,这899的手机和8999的iphone比起来没啥大区别,真的没啥大区别…一样用

WS2812b模块

前面两天虽然有一些波折,不过WS2812b 8X8的阵列显示驱动还是写出来,自然要整合到现有的midemo里面,这样使用方法就和其他命令一样了,就酱紫调用:

2812DISPLAY,str

然后就会按照从左到右滚动方式显str字符串,就这样简单。 显示完成后会受到消息,提示滚屏显示完成 WS2812B_TRANSMIT_DONE XXXXX

下面是源码,基本就是把昨天写代码按midemo的demo套一下就可以了,执行效果也不错,很顺利。

菜鸟学写Cat1 Demo集(连载)

有一点值得注意,SPI驱动由于数据运算量大,已经明显看出吃CPU了,特别是和实时刷新的OLED进行CPU分速后,显示慢了不少,不过还算能接受吧,您可以根据自己的需求把功能裁减下去一些,速度就快了。 方法就是配置bs.lua文件,文件内容一看就懂,基本不需要解释。

硬件连接方式如下,ws2812b采用的是单总线结构,把DIN连接到SPI的DO就可以了,电源我是用的是VLCD和OLED是一个电源,模块的这组供电驱动能力还是很强的,几百ma都不在话下。
菜鸟学写Cat1 Demo集(连载)

下面是代码,您直接从github下载就可以,建议装一个gitgui,这样每次可以自动同步了,更省事。

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:ws2812b 8X8矩阵屏幕字符驱动
-- @author miuser
-- @module midemo.ws2812b
-- @license MIT
-- @copyright [email protected]
-- @release 2020-10-10
--------------------------------------------------------------------------
-- @使用方法 2812DISPLAY,str 显示一个字符
-- @显示完成后,系统收到消息 WS2812B_TRANSMIT_DONE XXXXX
-- @显示完成后,终端收到消息 XXXXX transmited to WSMatrix 

require "bit"

module(...,package.seeall)
--英文字符集
local lib_5X7={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x14,0x7F,0x14,0x7F,0x14,0x24,0x2A,0x07,0x2A,0x12,0x23,0x13,0x08,0x64,0x62,0x37,0x49,0x55,0x22,0x50,0x00,0x05,0x03,0x00,0x00,0x00,0x1C,0x22,0x41,0x00,0x00,0x41,0x22,0x1C,0x00,0x08,0x2A,0x1C,0x2A,0x08,0x08,0x08,0x3E,0x08,0x08,0x00,0x50,0x30,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x60,0x60,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x3E,0x51,0x49,0x45,0x3E,0x00,0x42,0x7F,0x40,0x00,0x42,0x61,0x51,0x49,0x46,0x21,0x41,0x45,0x4B,0x31,0x18,0x14,0x12,0x7F,0x10,0x27,0x45,0x45,0x45,0x39,0x3C,0x4A,0x49,0x49,0x30,0x01,0x71,0x09,0x05,0x03,0x36,0x49,0x49,0x49,0x36,0x06,0x49,0x49,0x29,0x1E,0x00,0x36,0x36,0x00,0x00,0x00,0x56,0x36,0x00,0x00,0x00,0x08,0x14,0x22,0x41,0x14,0x14,0x14,0x14,0x14,0x41,0x22,0x14,0x08,0x00,0x02,0x01,0x51,0x09,0x06,0x32,0x49,0x79,0x41,0x3E,0x7E,0x11,0x11,0x11,0x7E,0x7F,0x49,0x49,0x49,0x36,0x3E,0x41,0x41,0x41,0x22,0x7F,0x41,0x41,0x22,0x1C,0x7F,0x49,0x49,0x49,0x41,0x7F,0x09,0x09,0x01,0x01,0x3E,0x41,0x41,0x51,0x32,0x7F,0x08,0x08,0x08,0x7F,0x00,0x41,0x7F,0x41,0x00,0x20,0x40,0x41,0x3F,0x01,0x7F,0x08,0x14,0x22,0x41,0x7F,0x40,0x40,0x40,0x40,0x7F,0x02,0x04,0x02,0x7F,0x7F,0x04,0x08,0x10,0x7F,0x3E,0x41,0x41,0x41,0x3E,0x7F,0x09,0x09,0x09,0x06,0x3E,0x41,0x51,0x21,0x5E,0x7F,0x09,0x19,0x29,0x46,0x46,0x49,0x49,0x49,0x31,0x01,0x01,0x7F,0x01,0x01,0x3F,0x40,0x40,0x40,0x3F,0x1F,0x20,0x40,0x20,0x1F,0x7F,0x20,0x18,0x20,0x7F,0x63,0x14,0x08,0x14,0x63,0x03,0x04,0x78,0x04,0x03,0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x7F,0x41,0x41,0x02,0x04,0x08,0x10,0x20,0x41,0x41,0x7F,0x00,0x00,0x04,0x02,0x01,0x02,0x04,0x40,0x40,0x40,0x40,0x40,0x00,0x01,0x02,0x04,0x00,0x20,0x54,0x54,0x54,0x78,0x7F,0x48,0x44,0x44,0x38,0x38,0x44,0x44,0x44,0x20,0x38,0x44,0x44,0x48,0x7F,0x38,0x54,0x54,0x54,0x18,0x08,0x7E,0x09,0x01,0x02,0x08,0x14,0x54,0x54,0x3C,0x7F,0x08,0x04,0x04,0x78,0x00,0x44,0x7D,0x40,0x00,0x20,0x40,0x44,0x3D,0x00,0x00,0x7F,0x10,0x28,0x44,0x00,0x41,0x7F,0x40,0x00,0x7C,0x04,0x18,0x04,0x78,0x7C,0x08,0x04,0x04,0x78,0x38,0x44,0x44,0x44,0x38,0x7C,0x14,0x14,0x14,0x08,0x08,0x14,0x14,0x18,0x7C,0x7C,0x08,0x04,0x04,0x08,0x48,0x54,0x54,0x54,0x20,0x04,0x3F,0x44,0x40,0x20,0x3C,0x40,0x40,0x20,0x7C,0x1C,0x20,0x40,0x20,0x1C,0x3C,0x40,0x30,0x40,0x3C,0x44,0x28,0x10,0x28,0x44,0x0C,0x50,0x50,0x50,0x3C,0x44,0x64,0x54,0x4C,0x44,0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x41,0x36,0x08,0x00,0x02,0x01,0x02,0x04,0x02,0xff,0xff,0xff,0xff,0xff}
Xmax=8
Ymax=8
--8X8二维数组存储点阵信息
local Screen_R={}
local Screen_G={}
local Screen_B={}
--前景笔刷颜色 R,G,B
local FrontColor={255,255,255}

--产生一个字节的发送码
local function genByte2(databyte)
    --按硬件编码送出的字节
    local ret=""
    local zero=0xC0
    local one=0xF8
    --每个section由6位构成,用8位字节表示
    local section={}
    for i=1,8 do
        section[i]= (bit.band(databyte,bit.lshift(0x01,8-i))==0) and zero or one
        ret=ret..string.char(section[i])
    end
    return ret
end
local function genByte3(databyte)
    --按硬件编码送出的字节
    local hwdatabyte=""
    local zero=0x04
    local one=0x06
    --每个section由3位构成,用8位字节表示,但仅前三位有效
    local section={}
    for i=1,8 do
        section[i]= (bit.band(databyte,bit.lshift(0x01,8-i))==0) and zero or one
    end
    local byte1=bit.bor(bit.lshift(section[1],5),bit.lshift(section[2],2),bit.rshift(section[3],1))
    local byte2=bit.band(bit.bor(bit.lshift(section[3],7),bit.lshift(section[4],4),bit.lshift(section[5],1),bit.rshift(section[6],2)),0xff)
    local byte3=bit.band(bit.bor(bit.lshift(section[6],6),bit.lshift(section[7],3),section[8]),0xff)
    --log.info("midemo.ws2812b","byte1="..byte1.." byte2="..byte2.." byte3="..byte3)
    local ret=string.char(byte1)..string.char(byte2)..string.char(byte3)
    --log.info("midemo.ws2812b","bytes="..string.toHex(ret))
    return ret
end

--产生一个字节的发送码
local function genByte(databyte)
    --按硬件编码送出的字节

    local zero=0x20
    local one=0x3E
    --每个section由6位构成,用8位字节表示,但仅前6位有效
    local section={}
    for i=1,8 do
        section[i]= (bit.band(databyte,bit.lshift(0x01,8-i))==0) and zero or one
        --log.info("midemo.ws2812b","section"..i.."="..section[i])
    end
    local byte1=bit.bor(bit.lshift(section[1],2),bit.rshift(section[2],4))
    local byte2=bit.band(bit.bor(bit.lshift(section[2],4),bit.rshift(section[3],2)),0xff)
    local byte3=bit.band(bit.bor(bit.lshift(section[3],6),section[4]),0xff)
    local byte4=bit.bor(bit.lshift(section[5],2),bit.rshift(section[4],4))
    local byte5=bit.band(bit.bor(bit.lshift(section[6],4),bit.rshift(section[7],2)),0xff)
    local byte6=bit.band(bit.bor(bit.lshift(section[7],6),section[8]),0xff)
    --log.info("midemo.ws2812b","byte1="..byte1.." byte2="..byte2.." byte3="..byte3)
    local ret=string.char(byte1)..string.char(byte2)..string.char(byte3)..string.char(byte4)..string.char(byte5)..string.char(byte6)
    --log.info("midemo.ws2812b","bytes="..string.toHex(ret))
    return ret
end

local function genDot(R,G,B)
    --log.info("midemo.ws2812b","R="..R.." G="..G.." B="..B)
    return genByte(G)..genByte(R)..genByte(B)

end

--初始化屏幕
function init()
    for i=1,8 do
        Screen_R[i]={}
        Screen_G[i]={}
        Screen_B[i]={}
    end
    local result = spi.setup(spi.SPI_1,0,0,8,5200000,0)--初始化spi,
    log.info("midemo.ws2812b","init",result)
    local PanelMatrix=""
    for y=1,Ymax do
        for x=1,Xmax do
            Screen_R[x][y]=0
            Screen_G[x][y]=0
            Screen_B[x][y]=0
            PanelMatrix=PanelMatrix..genDot(Screen_R[x][y],Screen_G[x][y],Screen_B[x][y])
        end
    end
end

--刷新屏幕
function update()
    local PanelMatrix=""
    for y=1,Ymax do
        for x=1,Xmax do
            local dot=""
            if (y%2==0) then 
                dot=genDot(Screen_R[8-x+1][y],Screen_G[8-x+1][y],Screen_B[8-x+1][y])

            else
                dot=genDot(Screen_R[x][y],Screen_G[x][y],Screen_B[x][y])
            end
            PanelMatrix=PanelMatrix..dot
        end
    end
    spi.send(spi.SPI_1,PanelMatrix)
end

--清屏
function clr()
    for y=1,Ymax do
        for x=1,Xmax do
            Screen_R[x][y]=0
            Screen_G[x][y]=0
            Screen_B[x][y]=0
        end
    end
end

--画点
function DrawDot(x,y)
    --log.info("midemo.ws2812b","x="..x.." y="..y)
    Screen_R[x][y]=FrontColor[1]
    Screen_G[x][y]=FrontColor[2]
    Screen_B[x][y]=FrontColor[3]
end

--在指定坐标处显示一个字符
function ShowChar(xpos, Show_char)
    c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量
    for x = 1, 5 do -- 循环5次(5列)
        line=lib_5X7[5*c+x]
        if ((x+xpos)>8 or (x+xpos)<1)then 
              --log.info("midemo.ws2812b","jump line because x+pos=",tostring(x+xpos))
        else
            for y = 1, 8 do
                if (line~=nil) then 
                    if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_R[x+xpos][y]=FrontColor[1] end
                    if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_G[x+xpos][y]=FrontColor[2] end
                    if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_B[x+xpos][y]=FrontColor[3] end
                    --log.info("midemo.ws2812b","Screen_R"..x+xpos.."="..FrontColor[1])
                    -- log.info("midemo.ws2812b","Screen_G"..x+xpos.."="..FrontColor[1])
                    -- log.info("midemo.ws2812b","Screen_B"..x+xpos.."="..FrontColor[2])
                end
            end
        end
    end   
end

--显示背景花纹
function DrawRandBackground(i)
    for yy=1,Ymax do
        for xx=1,Xmax do
            if (i%6)==0 then val1=1 val2=0 val3=1 end
            if (i%6)==1 then val1=2 val2=0 val3=0 end
            if (i%6)==2 then val1=1 val2=1 val3=0 end
            if (i%6)==3 then val1=0 val2=2 val3=0 end
            if (i%6)==4 then val1=0 val2=1 val3=1 end
            if (i%6)==5 then val1=0 val2=0 val3=2 end
            --log.info("midemo.ws2812b","rand="..val1)
            Screen_R[xx][yy]=0
            Screen_G[xx][yy]=0
            Screen_B[xx][yy]=0
        end
    end
end



--显示一个字符串
function ShowString(ShowStr)
    running=1
    i=0
    sys.taskInit(function()
        --ShowStr=" HELLO LUAT!"
        log.info("midemo.ws2812b","restarting w2812b")
        while running==1 do
            log.info("midemo.ws2812b","refreshing w2812b")
            DrawRandBackground(i)
            FrontColor={255,0,0}
            for ii=1,#ShowStr do
                str=string.sub(ShowStr,ii,ii)
                pos=(ii-1)*6-i
                --log.info("midemo.ws2812b","showing str "..str.." at pos "..pos)
                if (ii%6==0) then FrontColor={255,255,255} end
                if (ii%6==1) then FrontColor={255,0,255} end
                if (ii%6==2) then FrontColor={0,255,255} end
                if (ii%6==3) then FrontColor={255,0,255} end
                if (ii%6==4) then FrontColor={255,255,0} end
                if (ii%6==5) then FrontColor={0,255,255} end
                ShowChar(pos,str)
            end
            update()
            collectgarbage("collect")
            sys.wait(30) 
            i=i+1
            if i>=#ShowStr*6 then 
                i=0
                running=0
                sys.publish("WS2812B_TRANSMIT_DONE",ShowStr)
                write(ShowStr.." transmited to WSMatrix\r\n")
            end
        end
    end)

end

--初始化WS2812b
init()

--通过这个回调函数可以响应任意的串口或网络发布的命令
sys.subscribe("2812DISPLAY",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    str=arg[1]
    --b=arg[2]
    --c=arg[3]
    --通过write函数可以向串口和网络上报您的信息
    ShowString(str)
end)


--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end

现在这模块的稳定性还只能做到DEMO级别,用作工控环境不大行,这个暂时没办法了。
菜鸟学写Cat1 Demo集(四十七) SPI驱动WS2812b 2020-10-09

八卦

时有风吹幡动。一僧曰风动,一僧曰幡动。议论不已。惠能进曰:‘非风动,非幡动,仁者心动。 按这个套路我山寨一个。 一程序跑不起来,昨天觉得是底层有bug,今天觉得是脚本有bug,搞不大定。 懒懒笨笨打了个哈欠说: 既不是脚本bug,也不是底层bug,是你手法不对。

我最擅长做的一件事就是打脸,而且是打自己的脸,啪啪啪! 昨天调试SPI的驱动,搞了一天没搞定就料定是合宙luat底层架构有问题。 结果才发现原来是过去的算法发送的SPI频率过高,数据量过大造成的,优化了下算法,效果杠杠的。 这个事推而广之,很多时候我们料定环境不好的事,往往是由于我们的姿势不对,同样是跳水,有的人就没水花,有的人就能掀翻半池子水,就是这个道理。

SPI驱动WS2812B三色灯

昨天出了问题以后,我主要就重温了一下代码,我是用72位SPI的bit来模拟一个WS2812b的数据位,这样的好处是写代码省事,坏处是本来ws2812b频率就比较高,单个bit的周期只有1.25uS,再被24分频,发送的spi频率就要提高到57.6M才行。 这样底层的处理压力肯定比较大,另外,一帧的数据本来只有64*24=1.536KB,经过24分频,就变成了36.864KB,占用的内存相应的也就提高了。 我不清楚底层的SPI驱动是如何完成的,但有一点肯定,为了实现多任务,必定要有缓冲区,那么如果数据量大,缓冲区发送不及时被填满的几率也就相应的会增大,这都可能导致硬件复位。

综上,我对原有代码进行了优化,改进后的代码由6个bit来模拟一个ws2816的bit,相应的频率也就讲到了4.8M,程序稳定性大大提高,我有稍微封装了一下,这样可以用单一指令,显示单一字符串

init()
ShowString("Hello Luat!",cb)

菜鸟学写Cat1 Demo集(连载)

使用炒鸡简单,就一条指令初始化,一条指令执行显示,字符会从右测向左侧移入。 下面是代码,说实话比较繁杂,我是即写既忘,明天您再问我怎么写的我就说不上来了,其实现在也忘得差不多了

--- 模块功能:WS2812驱动
-- Author:[email protected]
-- License:MIT
-- Air模块            LCD
-- GND-------------地
-- LCD_DATA--------数据
-- VDDIO-----------电源

require "bit"

module(...,package.seeall)
--英文字符集
local lib_5X7={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x14,0x7F,0x14,0x7F,0x14,0x24,0x2A,0x07,0x2A,0x12,0x23,0x13,0x08,0x64,0x62,0x37,0x49,0x55,0x22,0x50,0x00,0x05,0x03,0x00,0x00,0x00,0x1C,0x22,0x41,0x00,0x00,0x41,0x22,0x1C,0x00,0x08,0x2A,0x1C,0x2A,0x08,0x08,0x08,0x3E,0x08,0x08,0x00,0x50,0x30,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x60,0x60,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x3E,0x51,0x49,0x45,0x3E,0x00,0x42,0x7F,0x40,0x00,0x42,0x61,0x51,0x49,0x46,0x21,0x41,0x45,0x4B,0x31,0x18,0x14,0x12,0x7F,0x10,0x27,0x45,0x45,0x45,0x39,0x3C,0x4A,0x49,0x49,0x30,0x01,0x71,0x09,0x05,0x03,0x36,0x49,0x49,0x49,0x36,0x06,0x49,0x49,0x29,0x1E,0x00,0x36,0x36,0x00,0x00,0x00,0x56,0x36,0x00,0x00,0x00,0x08,0x14,0x22,0x41,0x14,0x14,0x14,0x14,0x14,0x41,0x22,0x14,0x08,0x00,0x02,0x01,0x51,0x09,0x06,0x32,0x49,0x79,0x41,0x3E,0x7E,0x11,0x11,0x11,0x7E,0x7F,0x49,0x49,0x49,0x36,0x3E,0x41,0x41,0x41,0x22,0x7F,0x41,0x41,0x22,0x1C,0x7F,0x49,0x49,0x49,0x41,0x7F,0x09,0x09,0x01,0x01,0x3E,0x41,0x41,0x51,0x32,0x7F,0x08,0x08,0x08,0x7F,0x00,0x41,0x7F,0x41,0x00,0x20,0x40,0x41,0x3F,0x01,0x7F,0x08,0x14,0x22,0x41,0x7F,0x40,0x40,0x40,0x40,0x7F,0x02,0x04,0x02,0x7F,0x7F,0x04,0x08,0x10,0x7F,0x3E,0x41,0x41,0x41,0x3E,0x7F,0x09,0x09,0x09,0x06,0x3E,0x41,0x51,0x21,0x5E,0x7F,0x09,0x19,0x29,0x46,0x46,0x49,0x49,0x49,0x31,0x01,0x01,0x7F,0x01,0x01,0x3F,0x40,0x40,0x40,0x3F,0x1F,0x20,0x40,0x20,0x1F,0x7F,0x20,0x18,0x20,0x7F,0x63,0x14,0x08,0x14,0x63,0x03,0x04,0x78,0x04,0x03,0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x7F,0x41,0x41,0x02,0x04,0x08,0x10,0x20,0x41,0x41,0x7F,0x00,0x00,0x04,0x02,0x01,0x02,0x04,0x40,0x40,0x40,0x40,0x40,0x00,0x01,0x02,0x04,0x00,0x20,0x54,0x54,0x54,0x78,0x7F,0x48,0x44,0x44,0x38,0x38,0x44,0x44,0x44,0x20,0x38,0x44,0x44,0x48,0x7F,0x38,0x54,0x54,0x54,0x18,0x08,0x7E,0x09,0x01,0x02,0x08,0x14,0x54,0x54,0x3C,0x7F,0x08,0x04,0x04,0x78,0x00,0x44,0x7D,0x40,0x00,0x20,0x40,0x44,0x3D,0x00,0x00,0x7F,0x10,0x28,0x44,0x00,0x41,0x7F,0x40,0x00,0x7C,0x04,0x18,0x04,0x78,0x7C,0x08,0x04,0x04,0x78,0x38,0x44,0x44,0x44,0x38,0x7C,0x14,0x14,0x14,0x08,0x08,0x14,0x14,0x18,0x7C,0x7C,0x08,0x04,0x04,0x08,0x48,0x54,0x54,0x54,0x20,0x04,0x3F,0x44,0x40,0x20,0x3C,0x40,0x40,0x20,0x7C,0x1C,0x20,0x40,0x20,0x1C,0x3C,0x40,0x30,0x40,0x3C,0x44,0x28,0x10,0x28,0x44,0x0C,0x50,0x50,0x50,0x3C,0x44,0x64,0x54,0x4C,0x44,0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x41,0x36,0x08,0x00,0x02,0x01,0x02,0x04,0x02,0xff,0xff,0xff,0xff,0xff}
Xmax=8
Ymax=8
--8X8二维数组存储点阵信息
local Screen_R={}
local Screen_G={}
local Screen_B={}
--前景笔刷颜色 R,G,B
local FrontColor={255,255,255}

--产生一个字节的发送码
local function genByte(databyte)
    --按硬件编码送出的字节
    local hwdatabyte=""
    local zero=0x20
    local one=0x3E
    --每个section由6位构成,用8位字节表示,但仅前6位有效
    local section={}
    for i=1,8 do
        section[i]= (bit.band(databyte,bit.lshift(0x01,8-i))==0) and zero or one
        --log.info("midemo.ws2812b","section"..i.."="..section[i])
    end
    local byte1=bit.bor(bit.lshift(section[1],2),bit.rshift(section[2],4))
    local byte2=bit.band(bit.bor(bit.lshift(section[2],4),bit.rshift(section[3],2)),0xff)
    local byte3=bit.band(bit.bor(bit.lshift(section[3],6),section[4]),0xff)
    local byte4=bit.bor(bit.lshift(section[5],2),bit.rshift(section[4],4))
    local byte5=bit.band(bit.bor(bit.lshift(section[6],4),bit.rshift(section[7],2)),0xff)
    local byte6=bit.band(bit.bor(bit.lshift(section[7],6),section[8]),0xff)
    --log.info("midemo.ws2812b","byte1="..byte1.." byte2="..byte2.." byte3="..byte3)
    local ret=string.char(byte1)..string.char(byte2)..string.char(byte3)..string.char(byte4)..string.char(byte5)..string.char(byte6)
    --log.info("midemo.ws2812b","bytes="..string.toHex(ret))
    return ret
end

local function genDot(R,G,B)
    --log.info("midemo.ws2812b","R="..R.." G="..G.." B="..B)
    return genByte(G)..genByte(R)..genByte(B)

end

--初始化屏幕
function init()
    for i=1,8 do
        Screen_R[i]={}
        Screen_G[i]={}
        Screen_B[i]={}
    end
    local result = spi.setup(spi.SPI_1,0,0,8,4800000,1,1)--初始化spi,
    log.info("midemo.ws2812b","init",result)
    local PanelMatrix=""
    for y=1,Ymax do
        for x=1,Xmax do
            Screen_R[x][y]=0
            Screen_G[x][y]=0
            Screen_B[x][y]=0
            PanelMatrix=PanelMatrix..genDot(Screen_R[x][y],Screen_G[x][y],Screen_B[x][y])
        end
    end
end

--刷新屏幕
function update()
    local PanelMatrix=""
    for y=1,Ymax do
        for x=1,Xmax do
            local dot=""
            if (y%2==0) then 
                dot=genDot(Screen_R[8-x+1][y],Screen_G[8-x+1][y],Screen_B[8-x+1][y])

            else
                dot=genDot(Screen_R[x][y],Screen_G[x][y],Screen_B[x][y])
            end
            PanelMatrix=PanelMatrix..dot
        end
    end
    spi.send(spi.SPI_1,PanelMatrix)
end

--清屏
function clr()
    for y=1,Ymax do
        for x=1,Xmax do
            Screen_R[x][y]=0
            Screen_G[x][y]=0
            Screen_B[x][y]=0
        end
    end
end

--画点
function DrawDot(x,y)
    --log.info("midemo.ws2812b","x="..x.." y="..y)
    Screen_R[x][y]=FrontColor[1]
    Screen_G[x][y]=FrontColor[2]
    Screen_B[x][y]=FrontColor[3]
end

--在指定坐标处显示一个字符
function ShowChar(xpos, Show_char)
    c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量
    for x = 1, 5 do -- 循环5次(5列)
        line=lib_5X7[5*c+x]
        if ((x+xpos)>8 or (x+xpos)<1)then 
              --log.info("midemo.ws2812b","jump line because x+pos=",tostring(x+xpos))
        else
            for y = 1, 8 do

                if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_R[x+xpos][y]=FrontColor[1] end
                if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_G[x+xpos][y]=FrontColor[2] end
                if (bit.band(line,bit.lshift(0x01,8-y))~=0) then Screen_B[x+xpos][y]=FrontColor[3] end
                --log.info("midemo.ws2812b","Screen_R"..x+xpos.."="..FrontColor[1])
                -- log.info("midemo.ws2812b","Screen_G"..x+xpos.."="..FrontColor[1])
                -- log.info("midemo.ws2812b","Screen_B"..x+xpos.."="..FrontColor[2])
            end
        end
    end   
end

--显示背景花纹
function DrawRandBackground()
    for yy=1,Ymax do
        for xx=1,Xmax do
            val1=math.random(1,2)
            val2=math.random(1,2)
            val3=math.random(1,2)
            --log.info("midemo.ws2812b","rand="..val1)
            Screen_R[xx][yy]=0
            Screen_G[xx][yy]=0
            Screen_B[xx][yy]=0
        end
    end
end

--显示一个字符串
function ShowString(ShowStr,callback)
i=0
running=1
sys.taskInit(function()
    sys.wait(3000)
    --ShowStr=" HELLO LUAT!"
    while running do
        log.info("midemo.ws2812b","refreshing w2812b")
        DrawRandBackground()
        FrontColor={255,0,0}
        for ii=1,#ShowStr do
            str=string.sub(ShowStr,ii,ii)
            pos=(ii-1)*6-i
            --log.info("midemo.ws2812b","showing str "..str.." at pos "..pos)
            --if (ii%6==1) then FrontColor={255,0,255} end
            --if (ii%6==2) then FrontColor={0,255,255} end
            --if (ii%6==3) then FrontColor={255,0,255} end
            --if (ii%6==4) then FrontColor={255,255,0} end
            --if (ii%6==5) then FrontColor={0,255,255} end
            ShowChar(pos,str)
        end
        update()
        collectgarbage("collect")
        sys.wait(30) 
        i=i+1
        if i>=#ShowStr*6 then 
            running=0 
            callback()
        end
    end
end)

end

--初始化WS2812b
init()

function show()
    ShowString("Hello Luat!",show)
end
show()

这种比较基础的工作,其实难度并不大,无非就是bit位对齐,裁剪,拼接,分发。 您知道怎么用就够了,这代码读了价值很有限,特别是我开源了,您直接拿去用就可以了。

中间有一些注释的代码主要是更改字体的颜色,注释掉的话,文字就变彩色的了,现在是默认白色

            --if (ii%6==1) then FrontColor={255,0,255} end
            --if (ii%6==2) then FrontColor={0,255,255} end
            --if (ii%6==3) then FrontColor={255,0,255} end
            --if (ii%6==4) then FrontColor={255,255,0} end
            --if (ii%6==5) then FrontColor={0,255,255} end

背景如果您嫌太单调,还可以增加一点底色花纹

        Screen_R[xx][yy]=0
        Screen_G[xx][yy]=0
        Screen_B[xx][yy]=0

把这几句改一下

        Screen_R[xx][yy]=0
        Screen_G[xx][yy]=val2
        Screen_B[xx][yy]=val3

比如这样就可以了

今天又写了一天代码,偷个懒,明天再放到demo集里面

菜鸟学写Cat1 Demo集(四十六) 初尝SPI 2020-10-08

八卦

凡事都有个开始结束,其实想一想,早一点结束失败的创业过程并不一定是坏事,况且说,我其实那也不叫创业,无非是给自己找个好听的名字挥霍一些钱财,满足一下自己的爱好而已。 昨天投了几份简历,基本都在深圳。 投的职位都是我原来干过的那些勾当,比如供应链管理,比如新产品导入支持之类的。 话说这5年社会变化可是不小,博士满天飞,海龟也待业。 我这一个破本科证重新敲开一线企业的大门的概率只能跟摸彩票相提并论,不过反正也没啥成本,先投了再说。 总不能一上来就奔八线小企业投吧。

必须承认,企业越大打工的性价比越高。 这很合乎逻辑,因为分工合作越细,企业运作效率越高,特别是科技型企业更是如此。 一个好产品研发出来,卖出去一万件,和一千万件的研发成本进行摊薄,肯定是企业越大越划算。 不过小企业也有好处,主要是锻炼人,再有就是比较接地气,利于锻炼自己单干。 但我现在这个状态,除了挣钱,别的真心考虑不了,所以一门心思,就怎么来钱快,怎么找工作了。

SPI驱动WS2812B三色灯

今天捣鼓这个花了有十来个小时了吧,结果是失败… 很多时候,开发耗费的时间的大头,不取决于做出来的那些东西,而是没做出来的。 做出来的东西,越高越顺,其实开发时间相对可控,干着也爽。但试错就不同了,试错的成本很高,且运气成分也不小,所以来说如果在一个工作强度很大的环境下,愿意试错的人就会越来越少,最后使产品变得干巴巴。因为特别好用的功能不少都是有”巧劲儿”的成分,自己把这样的项目揽在手里,无异于搬起石头砸自己脚,我肯定是不去做的。 不过现在不一样,我是弄着玩,啥骨头都敢啃,大不了可以不做了。

结果今天就吃了闭门羹! 郁闷!

想讲一下思路吧, 通过查阅WS2812B的资料的得知,它是通过高、低电平的比例来判断逻辑1,和0的。 两高一低为逻辑“1”,一高两低为逻辑“0”,周期比较短,一位的逻辑状态只占1.25uS,且一个逻辑起码还要拆成3段,算起来也就说串行输出的频率最低不能低于2.4M才行,这样的频率GPIO肯定做不到,就需要用SPI总线,这个几十兆也轻松能上得到。

我这人比较懒,就想图省事,既然SPI很快,所以2:1的逻辑很容易实现。 就发送 0xFFFF00,就可以发送逻辑“1”, 发送0xFF0000就可以发送逻辑“0”,频率的话1位逻辑电平用24个bit来表示,换算过来19M就可以,因为容许误差,可以设置成20M。

但是测试中发现,程序运行几秒钟就死掉了,估计是SPI底层有bug。 所以这个模块基本上短期开发不出来了… 对不起

--- 模块功能:W2812驱动
-- Air模块            LCD
-- GND-------------地
-- LCD_DATA--------数据
-- VDDIO-----------电源
require "bit"

module(...,package.seeall)
ScreenData=""
DotDat=""
W=string.char(0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00)
BL=string.char(0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00)
R=string.char(0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00)
G=string.char(0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00)
B=string.char(0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xF0,0x00,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00,0xFF,0xFF,0x00)

--初始化屏幕
function init()
    local result = spi.setup(spi.SPI_1,0,0,8,20000000,1,1)--初始化spi,
    log.info("spi1",spi.SPI_1)
    log.info("testSpi.init",result)
    for i=1,64 do
        ScreenData=ScreenData..BL
    end
end
--屏幕全黑
function clear()
    ScreenData=""
    for i=1,64 do
        ScreenData=ScreenData..BL
        spi.send(spi.SPI_1,ScreenData)
    end

end
--刷新屏幕
function update()
    spi.send(spi.SPI_1,ScreenData)
end




--rgb565填充当前颜色 红绿蓝为0xF800 0x07E0 0x001F
function setcolor(color)
    DotDat=""
    --取出绿色分量
    g=bit.band(color,0x07E0)
    --6位色变换为8位色
    g=bit.rshift(g,5)*4
    for i=1,8 do
        if (bit.band(g,bit.lshift(0x01,8-i) )==0) then DotDat=DotDat..string.char(0xF0,0x00,0x00) else DotDat=DotDat..string.char(0xFF,0xFF,0x00) end
    end
    --取出红色分量
    r=bit.band(color,0xF800)
    --5位色变换为8位色
    r=bit.rshift(r,11)*8
    for i=1,8 do
        if (bit.band(r,bit.lshift(0x01,8-i) )==0) then DotDat=DotDat..string.char(0xF0,0x00,0x00) else DotDat=DotDat..string.char(0xFF,0xFF,0x00) end
    end
    --取出蓝色分量
    b=bit.band(color,0x001f)
    --5位色变换为8位色
    b=b*8
    for i=1,8 do
        if (bit.band(b,bit.lshift(0x01,8-i) )==0) then DotDat=DotDat..string.char(0xF0,0x00,0x00) else DotDat=DotDat..string.char(0xFF,0xFF,0x00) end
    end
    log.info("midemo.ws2812b","colorinfo is ",string.toHex(DotDat) )    
end

--在ws2812屏幕上按照当前颜色显示一个点
function DrawPixel(x,y)
    if (y%2==0) then x=8-x+1 end
    pos=((y-1)*8+x)*72
    log.info("midemo.ws2812b","pos="..pos)
    ScreenData=string.sub(ScreenData,1,pos-72)..DotDat..string.sub(ScreenData,pos,#ScreenData)
    -- collectgarbage("collect")
    -- log.info("midemo.ws2812b","memusage="..collectgarbage("count"))
end

x=1
y=1


init()
clear()

sys.taskInit(function()
    sys.wait(5000)
    while true do
        log.info("midemo.ws2812b","refreshing w2812b")
        clear()
        sys.wait(20) 
        setcolor(0xF800)
        DrawPixel(x,y)
        update()
        if (x==8) then 
            x=0 
            y=y+1 
        end
        if (y==9) then y=1 end
        x=x+1
        collectgarbage("collect")
        sys.wait(20) 
    end
end)

pmd.ldoset(15,pmd.LDO_VLCD)
pmd.ldoset(6,pmd.LDO_VMMC) 

您有兴趣也可以帮我研究下,如果能解决,我感激万分。 下面是我测试的效果,可以看出来,功能是没问题的,就是死机。

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(四十五) GPIO纯输出模块 2020-10-07

八卦

童话故事都是骗人的,可能是由于智商问题,我花了十几年才懂这道理。 任何一本书都会告诉你这样的行为是对的好的,合理的。 这样你就能安心的按照这个书里写的套路来,当然聪明人不完全照书本上的来,要稍加修改,把书上的东西变成自己的,自己得比原文作者更优秀一些…毕竟作了”优化”。 至少我是酱紫的。

我总是有一万种理由证明我自己做的是对的,好的,然后再把所有其他与我行为向左的批判一通,从中获取一点点的自信。

GPO模块

今天我发现一个问题,就是我竟然还没有写普通的GPIO模块,前一段我确实写了一个叫BIO的模块,可以通过SETGPIO设置端口为弱高电平或者弱低电平,通过GETGPIO获取引脚的高低电平。 但是并没有写一个普普通的GPIO输出模块,这两种模式的工作情况不同,通常情况下,使用SETGPIO,对应的引脚只能获得大概30uA的上拉、下拉电流,这在推动很多器件是不够大的。 所以如果需要推动继电器一类的负载,还是要用强制高低电平输出,这时候就用到了今天写的模块gpo,就是通过SETGPO,指令使引脚输出高电平或低电平,通过GETGPO指令读取输出引脚的高低电平。 需要指出的是,由于强制输出高低电平,所以即使外部引脚由于负载电流过重,没有被设置成期望的电平, 这样的过载设计,是不被允许的,所以也不在软件的考量范围内。 读入的仍然是输出指令给出的,而非实际的高低电平。

代码如下,非常简单,您一看就懂对吧

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM(Apache2)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:GPIO引脚输出控制
-- @author miuser
-- @module midemo.gpo
-- @license MIT
-- @copyright [email protected]
-- @release 2020-10-07
--------------------------------------------------------------------------
-- @使用方法
-- @串口收到形如SETGPO,pio,level的指令后,对应的pio引脚设置为level状态,比如 串口收到SETGPO,13,1,则GPIO13引脚被设置为高电平
-- @当串口收到SETGPO指令时,串口将收到中文提示 GPIOxx当前电平为高/低的提示
-- @串口收到形如GETGPO,pio的指令后,系统发送主题为GPIO_LEVEL 主题的消息,比如串口收到 GETGPO,13 将发送系统消息GPO_LEVEL,13,1 
-- @消息收发均采用utf8编码,与lua文件系统相同

require"pins"
require"utils"
require"pm"
require"common"

module(...,package.seeall)

--上一次的电平状态表,以管脚号为索引
local pinLastStage={}

--通过消息发送调试信息到串口模块
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end

write("Initialing GPO")


sys.subscribe("SETGPO",function(...)

    io=tonumber(arg[1])
    level=tonumber(arg[2])
    mute=tonumber(arg[3])
    --保存对应IO口状态,备查
    pinLastStage[io]=level
    pins.setup(io,level)              
    if (mute~=1) then 
        if (level==0) then write("GPOLEVEL,"..tostring(io)..",0".."\r\n") else write("GPOLEVEL,"..tostring(io)..",1".."\r\n") end 
        sys.publish("GPO_LEVEL",io,level)
    end   
end)

sys.subscribe("GETGPO",function(...)

    io=tonumber(arg[1])
    level=pinLastStage[io]
    if (level==0) then write("GPOLEVEL,"..tostring(io)..",0".."\r\n") else write("GPOLEVEL,"..tostring(io)..",1".."\r\n") end 
    sys.publish("GPIO_LEVEL",io,level)
end)

执行结果如下
菜鸟学写Cat1 Demo集(连载)


菜鸟学写Cat1 Demo集(四十四) 新功能小结 2020-10-06

八卦

今天发现几个云服务器陆陆续续都要到期了,续费费用很高,比如像阿里云2G4核这种续费年费都要接近2000块,却是负担不起。 好在以前没在腾讯薅羊毛过,这次弄了个三年的服务器,也才不到2000,我还挂了一个云盘。 折合一个月50块钱,跟我话费相当,勉强可以承受。

自从IT技术兴起后,杀熟成为了一种可靠的商业策略。 通过实名认证,可以有效地区分出新老用户,对于新用户,宁可赔本也要拉拢到自己这,即使不赚钱,起码还能赚个人头,这个对融资有帮助。 听说VC他们都认这个,每个金融投资者都梦寐以求成为能收人头税的那种机构。 对于熟客么,对不起,该收多少得收多少了,当然如果按照一锅粥论,还得额外加价,因为要补偿其他用户赔本的生意。 毕竟作为一个商业实体,主体不能长期持续性亏损。

做总结

每当我懒到,一行代码都不愿意写的时候,我就做总结。 这样可以在保持代码量一行不增的情况下看起来自己还不像是啥都没做。 今天就是这么个情况,我们简单回顾下过去这10来天做的事儿。
菜鸟学写Cat1 Demo集(连载)

从图中可以看出我们这10来天还是有进展的,当然这只是看起来有进展,因为我都调好捏的软柿子捏,难的模块一个没写,当然进度就显得很快。 下面是流水账,您不看也罢,看了别嫌我说废话。

1) 我们完善了网络通讯机制,通过iRTU软件包,我们成功的将串口和外部网络构成了对等的通讯接口,也就是说,无论您向本地串口发送指令,还是网络端口发送指令,这些指令执行效果基本是完全等价的,这样可以使您在远程端如同本地操作一样,类似于远程终端的效果,这个在几十年前有这么一项技术,英文叫telnet,中文叫超级终端,是在PC刚刚兴起时的一项基础技术,现在特种变种也是经久不衰,算是很基础的那种技术了。 当然,在物理网领域,这技术并不普遍,未来估计会随着硬件性能的提高慢慢普遍,我这就是先弄个玩具试试水。

2) 我么增加了帮助模块

通过输入HELP,可以查阅新增功能。

这模块因为没有搞定lua的正则表达式功能,写的显得非常的不到位,未来还将进行优化。 最好能支持 HELP CMDXXXX,这种按指令查询的方式,等以后慢慢来吧,今日复明日明日何其多,不急。

3)电话核心板总算是能打电话,发短信了。 可以完成诸如宠物防丢,老人紧急求助这样的功能。 按一下按钮,电话自动播出。 接听也是一键接听。 短信就比较简单了,单指令发送接收,用来发垃圾短信太方便了。

4) TTS语音模块,可以用来单指令发送语音命令,做个叫号器或者带读取的计算器妥妥的。

5) 我们新增了STH30温湿度传感器读取功能,这模块用的人太多了,不知道干啥用的您就百度SHT30就可以了

6) GPS,您去合宙官网买一个带天线的air530模块,然后连到串口1上就可以使电话核心板支持GPS了,非常简单,且支持AGPS功能定位很迅速快。 定位精度也就一般,因为不支持wifi室内定位,所以没有室内定位功能。 起码您得窗边碰碰运气,才能定位。

今天的总结就这样子了。

如果您想试试的话,非常简单。就去买我一块开发板,然后自己焊个模块上去,然后再下载这个demo的最新固件,把所有文件一股脑烧进去就行了。 有一点注意,因为现在支持的外挂模块越来越多,所以您需要自己配置下bs.lua文件,把不需要的功能关掉,这个应该一看就懂。

COM_UART_IDs={2,3,129}  
--GPIO13,18,19映射为双向IO端口
BIOPins={13,18,19}
--是否安装了128X32的OLED小显示屏
OLED=true
--是否安装了Air530模块,或者使用带有GPS功能的模块
GPS=true
--GPS功能使用的COM口号
GPSCOM=1
GPSBaudRate=9600

没装的外挂器件,就把ture改成false就可以了,不如您没有屏幕也没有oled就改成

--是否安装了128X32的OLED小显示屏
OLED=false
--是否安装了Air530模块,或者使用带有GPS功能的模块
GPS=false

这样就不会报错了。 bs.lua这个文件就是定义核心板的配置和外部电路连接用的。

菜鸟学写Cat1 Demo集(四十三) GPS定位 2020-10-05

八卦

今天收到了合宙生态企业飞思创大佬寄出的Air820开发板,还有一盒月饼… 我感动的眼泪汪汪的。 现在做个买卖真的是太难了,一边跪求客户买东西,一边跪求内部工程师好好卖力气工作,我这种三脚猫的友商大佬都得想着打点打点,让大家多多捧场。 说真的创业太TM苦了…

所以我决定不再继续受这洋罪了,把这个demo写完,去好好找找适合自己的工作,外出打工,养家糊口,从此创业之事再不敢有非分之想了。

GPS定位

GPS也是一个非常好写的模块,因为官方的DEMO已经非常清楚了,所以这个和SHT30差不多,就是连连线的赶脚,代码稍微拼凑就好了。 当然,也不是说一点注意事项都没有,下面我就絮叨絮叨:

1) 建议使用合宙官方的Air530模块成品,带天线的那种,支持可以兼容1.8V电平,跟Cat1 Phone Board连上就行了。 接上电源+5V GND, TX RX两根线交叉互联,就OK了

菜鸟学写Cat1 Demo集(连载)

2) 我连的是串口1,波特率9600

3) 为了加速定位建议使用AGPS,可以显著的提高定位速度,这东东基本上就是下载星历然后写到530模块里的作用。 但是要注意,这个功能好像挺占用内存的,所以,您悠着点用,不行就把TTS这类占内存多的功能拿掉。

今天已经开始偶然碰到模块内存不足的问题了,看来随着代码量上涨,Air724内存不足的问题开始暴露出来,明天可能需要写一个内存检测的小模块了。 好了今天就酱紫了,代码非常简单,您自己读读吧。我就不喂到嘴里了。

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:GPS查询功能
-- @author miuser
-- @module midemo.gpsplus
-- @license MIT
-- @copyright [email protected]
-- @release 2020-10-05
--------------------------------------------------------------------------
-- @使用方法
-- @将串口连接到串口1,当定位成功,则返回GPS,LOCATED SUCCESSFULLY
-- @通过GETGPS命令查询GPS状态
-- @返回如下格式数据:Satellite Located:TRUE/FALSE,Longitude:XX.XXXXX,Latitude:XX.XXXXX,Altitude:XX.XXXXX,Speed:xx.xxxxx,Course,XX.XXXXX,ViewedSatelliteCount:XX,UsedSatelliteCount:XX

require"gps"
--agps功能模块只能配合Air800或者Air530使用;如果不是这两款模块,不要打开agps功能
require"agps"
module(...,package.seeall)

FIXED=0
Longitude=0
Latitude=0
Altitude=0
Speed=0
Course=0
ViewedSatelliteCount=0
UsedSatelliteCount=0

local function printGps()
    if gps.isOpen() then
        local tLocation = gps.getLocation()
        local speed = gps.getSpeed()
        --保存GPS参数
        FIXED=gps.isFix()
        Longitude=tLocation.lngType..tLocation.lng
        Latitude=tLocation.latType..tLocation.lat
        Altitude=gps.getAltitude()
        Speed= gps.getSpeed()
        Course=gps.getCourse()
        ViewedSatelliteCount=gps.getViewedSateCnt()
        UsedSatelliteCount=gps.getUsedSateCnt()     

        log.info("midemo.gpsplus","satllite information",
            FIXED,
            Longitude,Latitude,
            Altitude,
            Speed,
            Course,
            ViewedSatelliteCount,
            UsedSatelliteCount)


    end
end


local function gpsCb(tag)
    log.info("midemo.gpsplus","located successfully",tag)
    printGps()
end

--gps.setPowerCbFnc,设置串口通信参数,Air530的波特率为9600
gps.setUart(bs.GPSCOM,bs.GPSBaudRate,8,uart.PAR_NONE,uart.STOP_1)
--GPS就会一直开启,永远不会关闭
gps.open(gps.DEFAULT,{tag="OPENGPS",cb=gpsCb})
sys.timerLoopStart(printGps,1000)

--通过这个回调函数可以响应任意的串口或网络发布的命令
sys.subscribe("GETGPS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    --a=arg[1]
    --b=arg[2]
    --c=arg[3]
    --通过write函数可以向串口和网络上报您的信息
    write("GPSLocated:"..tostring(FIXED)..",Longitude:"..tostring(Longitude)..",Latitude:"..tostring(Latitude)..",Altitude:"..tostring(Altitude)..",Speed:"..tostring(Speed)..",Course:"..tostring(Course)..",ViewedSatelliteCount:"..tostring(ViewedSatelliteCount)..",UsedSatelliteCount:"..tostring(UsedSatelliteCount).."\r\n")
end)


--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end

这次测试GPS效果,我用的是手机,其实和用LLCOM效果是一样的。对于mi-demo,手机控制和本地串口控制是完全等效的。

菜鸟学写Cat1 Demo集(连载)

返回的数据有经纬度,高度,速度,方向,可见的卫星数和用来定位的卫星数。

卫星数我解释一下,目前的卫星定位系统通常必须同一系统的微型才能实现定位,所以虽然可见的卫星数虽然可能多达7-8个,但是只要同组的卫星数不到3个,仍然无法定位的。 目前Air530一般只用到北斗和GPS两种方式,所以一定要保证北斗可见卫星数大于3,或者GPS可见卫星数大于3才可以。

还有一个事,为了腾出串口1给GPS用,我修改了bs.lua 硬件适配文件,就是把原来进行命令控制的串口里面拿掉了串口1,改GPS使用,这个文件您也顺便看一下吧。

----------------------引脚分配表-----------------------------
------------------------------------------------------------
------------------------------------------------------------
require "lbsLoc"
require "misc"
require "nvm"
module(...,package.seeall)
-- 将串口2,3及USB虚拟串口分配作为命令控制接口
COM_UART_IDs={2,3,129}  
--GPIO13,18,19映射为双向IO端口
BIOPins={13,18,19}
--是否安装了128X32的OLED小显示屏
OLED=true
--是否安装了Air530模块,或者使用带有GPS功能的模块
GPS=true
--GPS功能使用的COM口号
GPSCOM=1
GPSBaudRate=9600
------------------------------------------------------------

这是修改部分的代码,按说不难理解。 顺便说一下,如果您手里的核心板没装OLED,一定要设置成OLED=false,不然由于I2C异常,程序工作不正常

菜鸟学写Cat1 Demo集(四十二) 短信收发 2020-10-04

八卦

各位亲,有没有关注合宙的公众号“合宙Luat”,真心希望没有…因为上一篇公众号推文把我当”创业爱好者”时候的老底儿都给揭了。 需要说明的是,我的真实情况比公众号里发布的写的还要更惨一些。 我提供了过去创业活动的一些文字和图片素材给合宙市场部,他们根据这些素材辛苦进行了整理美化形成了那篇推文,我这也就只有感谢的份儿了。 老话说,艺术源于生活高于生活。 文化艺术作品都是这样的,不可能把真事儿都一五一十的说了,不然就没人看了。 借用电影《无双》里的一句话 “艺术,就是让我在堕落的人生之中看到美好的一面。” 我想至少合宙市场部的同事们做到了,确实牛!

不过说创业其实说来也是有好处的,真的! 我记得打工那时候,每年十几天的年假和病假似乎都不太够用,每年都得因为有事,或者生病歇几天,甚至十几天都不够歇的。 但我创业这几年就很少病休了,所以说创业绝对是强身健体,这是真事儿。

短信收发功能编写

今天电话核心板群有人点播短信收发功能,我就从命了。反正先写那个都是写。 功能很简单,照着合宙的官方demo稍微一改就成了。

发送短信使用 SMS,13XXXXXXXXX,发送的内容

如过收到了外部发来的短信,串口和网络会收到RECEIVED SMS 收到的内容 @收到的时间 这样的消息

下面是具体的例子,我用电话核心板和我的手机互发的短信

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

代码太简单了,我实在没啥必要解释了,就是注意下编码是GB2312,lua本身的内置编码是utf8,midemo架构的默认编码也是utf8,所以需要做一下转码,就酱紫了。 国庆假日过了4天了,继续汤泡饭。

--------------------------------------------------------------------------------------------------------------------------------------- 
-- 版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭,等
-- 项目源码重要贡献者:月落无声
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:短信收发
-- @author miuser
-- @module midemo.trsms
-- @license MIT
-- @copyright [email protected]
-- @release 2020-10-04
--------------------------------------------------------------------------
-- @使用方法
-- @发送短信 SMS,13XXXXXXXXX,发送的内容
-- @收到短信 RECEIVED SMS 收到的内容 @收到的时间
require"sms"
module(...,package.seeall)

--短信发送
sys.subscribe("SMS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    tel=arg[1]
    content=common.utf8ToGb2312(arg[2]) 
    sms.send(tel,content,function() write("SENDING "..tel.." "..arg[2].."\r\n") end)

end)

--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end


--短信接收
local function procnewsms(num,data,datetime)
    log.info("testSms.procnewsms",num,data,datetime)
    write("RECEIVED SMS FROM "..num.." "..common.gb2312ToUtf8(data) .."@"..tostring(datetime))
end
sms.setNewSmsCb(procnewsms)

菜鸟学写Cat1 Demo集(四十一) SHT30读取 2020-10-03

八卦

话说昨天前两天写TTS模块吃了亏,白浪费了两天时间。今天马上我就开始补课,经过了高人指点,我找到了最新版的固件,地址如下:
https://gitee.com/openLuat/Luat_Lua_Air724U/tree/master/core
话说,这个V0021的版本果然TTS把中文和英文,数字这些统统都支持了。 看来合宙研发的效率也不是随便说说的,但是人家低调,人家不说。 就等你自己去找。 不像我自己会了一点三脚猫的功夫就到处的瞎逼逼,生怕别人不知道。

对于每个版本的固件,相应的有一个叫releasenote的东东,我研究了一下,就挖到了宝,跟您分享下

一、core V21版本修改记录:
    1,485发送1200波特率下最后一字节错误
    2,disp.getframe()接口只能获取到图片的缓存,文字信息的缓存获取不到
    3,概率性lua虚拟机异常
    4,mono_std_spi_st7571.lua 无法正常使用
    5,拍照死机
    6,低功耗下用串口2打印日志有不完整
    7,温湿度传感器sht30,在开发板上工作不正常
    8,disp.close() 之后再执行disp.init 无提示直接重启
    9,支持通过模数和指数的rsa加解密功能接口
    lua添加耳机热插拔接口
二、lib修改记录:
    1、增加audio.setChannel(0)
    2、修正新版本luatools加密导致nvm运行异常的问题

看到了第7条了吧,支持sht30,这东西就牛Bility了。 话说,过去我弄了好久都没搞定,现在竟然支持了,更棒的是有热心网友曾经给过我SHT30的读取代码,我也不管三七二十一,就给他的代码贴出来了。 如果他告我侵权,我就惨了…

SHT30读取

今天midemo新增了SHT30的读取接口,命令很简单,发送测试指令SHT30即可,立即可以从串口返回温度和湿度的值

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

因为Air724给客人用的I2C总线只有一条,所以要和我们的SSD1306的显示屏共用一下,带来的副作用就是读取的时候屏幕稍稍的卡顿一下,好在我们也不用OLED看电影,卡一下就凑合凑合吧。

下面是代码,基本上就是月落无声大佬提供的测试代码和demo.lua进行了一下拼接,几分钟就好了。您也快来试试吧

--------------------------------------------------------------------------------------------------------------------------------------- 
-- 版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭,等
-- 项目源码重要贡献者:月落无声
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:STH30温湿度传感器读取
-- @author 月落无声
-- @module midemo.sht30
-- @license MIT
-- @copyright 月落无声@luat
-- @release 2020-10-03
--------------------------------------------------------------------------
-- @使用方法
-- @发送SHT30,返回当前的温湿度,格式为 Temperature read is XX.XX ,Humudity read is XX.XX


module(..., package.seeall)
local function i2c_open(id)
    if i2c.setup(id, i2c.SLOW) ~= i2c.SLOW then
        log.error("I2C.init is: ", "fail")
        log.error("I2C.init plis: ", i2c.setup(id, i2c.SLOW))
        i2c.close(id)
        return
    end
    return i2c.SLOW
end

function sht30read(id)
    local   temp,hum
    i2c.send(id,0x44,{0x2c,0x06})
    local sht30_data = i2c.recv(id,0x44,6)
    if sht30_data == nil then sht30_data = 0 end
    log.info("SHT30 HEX DATA:",string.toHex(sht30_data," "))
    if sht30_data ~= 0 then
    local _,h_H,h_L,h_crc,t_H,t_L,t_crc = pack.unpack(sht30_data,'b6')
    if h_H == nil then return end
        temp = ((1750*(h_H*256+h_L)/65535-450))/10
        hum = ((1000*(t_H*256+t_L)/65535))/10
        log.warn("SHT30 temp,humi:",temp,hum)
        return  temp,hum
    end
end

--通过这个回调函数可以响应任意的串口或网络发布的命令
sys.subscribe("SHT30",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    --a=arg[1]
    --b=arg[2]
    --c=arg[3]
    temp,hum=sht30read(0)
    write("Temperature:"..string.format("%0.2f",temp)..",Humudity:"..string.format("%0.2f",hum).."\r\n")
end)


--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)

end

菜鸟学写Cat1 Demo集(四十) TTS增强源码 (2020-10-02)

八卦

话说,昨天我提到官方的TTS不支持英文字母发声,今天一早就被打脸。 有合宙内部的消息灵通人士在群里回复说,其实官方的固件已经开始支持英语发音。 我估计很有可能是已经有了新版的固件,而我还在用V0017的老版固件的原因。 从这个事儿说明两点:

1)一定要和合宙的内部人士加强沟通,了解固件更新计划,不然很容易像我这样做无用功。

2)不要试图消耗巨量精力去修正成熟架构上缺陷,或者至少应该首先找正确官方途径反馈bug,确切没有修补计划的,再自己动手。

TTS增强源码

既然官方固件即将支持英文发音,所以其实我这个源码就没什么价值了。 本来就不想贴源码了…但后来想一想,虽然代码没啥用了,但是解题的思路,也许您可以借鉴下。 或者没准可以改一改做别的用,也未可知。 所以还是贴出完整的源码

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:TTSPLUS英文补丁版播报
-- @author miuser
-- @module midemo.ttsplus
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-30
--------------------------------------------------------------------------
-- @使用方法
-- @使用TTS,XXX 则朗读对应的XXX,其中XXX为中文或数字,不支持英文

module(...,package.seeall)
require"audio"

--通过这个回调函数响应TTS指令
sys.subscribe("TTS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    doc=arg[1]
    if doc==nil then doc="朗读内容为空" end
    --b=arg[2]
    --c=arg[3]
    --单次播放,音量等级7
    audio.setStrategy(1)
    audio.play(1,"TTS",doc,7)
    write("TTS:"..doc)
end)

--通过这个回调函数响应TTS补丁指令
sys.subscribe("PLUS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    doc=arg[1]
    if doc==nil then doc="朗读内容为空" end
    --b=arg[2]
    --c=arg[3]
    --单次播放,音量等级7
    audio.setStrategy(1)
    PLUS(doc)
    write("PLUS:"..doc)
end)
--通过这个回调函数响应TTSPLUS指令(打补丁后的TTS)
sys.subscribe("TTSPLUS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    doc=arg[1]
    if doc==nil then doc="朗读内容为空" end
    sdoc,type=TTS_Split(doc)

    sys.taskInit(function()
        for i=1,#type do
            if (type[i]=="tts") then
                audio.play(1,"TTS",sdoc[i],7,function() sys.publish("PLAY_"..sdoc[i].."_DONE") end )
                result, data = sys.waitUntil("PLAY_"..sdoc[i].."_DONE", 30000)
            else
                PLUS(sdoc[i])
                result, data = sys.waitUntil("PLAY_"..sdoc[i].."_DONE", 30000)
            end
        end
    end)   
    write("TTSPLUS:"..doc)
end)


--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end


function TTS_Split(input)
    --按照TTS是否包含数字,字母符号等不可直接发音的元素进行分段
    local ttsdoc={}
    --和上面的数组对其,如果段落对应的数字为1,则TTS支持直接发音,如果为0则不支持
    local ttstype={}

    --读取到的内容
    part=""
    --当前状态
    curr="tts"
    for i=1,#input do
        letter=input:sub(i,i)
        --是需要mp3发音的字符
        if ((string.byte(letter)>64 and string.byte(letter)<122) or letter:find("*")or letter:find("?")or letter:find("+")or letter:find("-")or letter:find("#")) then
            --小写字母转大写字母
            if (string.byte(letter)>96 and string.byte(letter)<123) then letter=string.char((string.byte(letter)-32)) end
            --模式切换,先保存切换前的文字到数组
            if (curr=="tts") then 
                table.insert(ttsdoc,part)
                table.insert(ttstype,"tts")
                log.info("midemo.tts","collected ",part)
                log.info("midemo.tts","type is ","tts")
                part=""
            end
            curr="mp3"
            part=part..letter
        else
            if (curr=="mp3") then 
                table.insert(ttsdoc,part)
                table.insert(ttstype,"mp3")
                log.info("midemo.tts","collected ",part)
                log.info("midemo.tts","type is ","mp3")
                part=""
            end
            curr="tts"
            part=part..letter
        end
    end
    table.insert(ttsdoc,part)
    table.insert(ttstype,curr)

    log.info("midemo.tts","ttsdoc",unpack(ttsdoc))
    log.info("midemo.tts","ttstype",unpack(ttstype))
    return ttsdoc,ttstype

end


function Play(filename)
    file,err=io.open("/lua/"..filename..(".mp3"))
    if (err~=nil) then 
        return
    else
        io.close(file)
    end
    audio.setStrategy(1)
    audio.play(1,"FILE","/lua/"..filename..(".mp3"),7,function() sys.publish("PLAY_"..filename.."_DONE") end)
end

--判断是否在播放中
playing=0
--是否中断当前播放
breakplay=0
function plusplay(s)
    result, data = sys.waitUntil("READY_TO_PLAY", 3000)
    playing=1
    for i=1,#s do
        filename=s:sub(i,i)
        if (filename=="*") then filename="Xing" end
        if (filename=="?") then filename="Wen" end
        if (filename=="+") then filename="Jia" end
        if (filename=="-") then filename="Jian" end
        if (filename=="#") then filename="Jing" end
        Play(filename)
        result, data = sys.waitUntil("PLAY_"..filename.."_DONE", 2000)
        if result == false then
            log.info("midemo.tts","ttsplus file play fail")
        end
        sys.wait(50)
        if (breakplay==1) then 
            log.info("midemo.tts","plusplay was broken") 
            sys.publish("READY_TO_PLAY")
            breakplay=0
            break
        end
    end
    playing=0
    sys.publish("PLAY_"..s.."_DONE")
end

function PLUS(s)
    sys.taskInit(plusplay,s)
    if (playing==0) then
        sys.publish("READY_TO_PLAY")
    else
        breakplay=1
    end
end

另外,说一下,这个补丁很贵,要占100K的flash,所以如果官方有正版出来,这模块就不建议您用了
菜鸟学写Cat1 Demo集(三十九) TTS增强 2020-10-1

八卦

刚看了一会电视,最大的感觉就是文化节目里面广告插的基本上都是非常简单直接的,关键我看的是央视一套节目,这种情况只有我小时候有过。但近些年并不是特别常见,气势也小了很多。 也许是我太久不看电视大惊小怪了。

TTS增强

昨天发现目前的Luatask系统不支持念英文字母。 我今天就琢磨着,怎么上手改一改,把这个TTS功能完善下,万一改成了呢。 基本思路也挺简单的。就是为二十六个英文字母建立26个mp3文件,当字符串里含有英文字母,就单提出来插播mp3文件。 说起来容易,但这里面有很多的细节

1)要判断TTS是否念完,才能插入MP3播放,播放完还得回到原来的TTS播放状态,播放下面的文字
2)连续的英文字母,需要判断播放完,再继续播放下一个
3)如果一串字符串没播完,又有新的播放指令进来,要把原来的播放序列取消,优先播放新的字符串

这些问题对于大佬肯定不是个事儿,但对于我这样的菜鸟还是有不小的难度。今天只写出来了一部分,就是通过拼接可以连续的念一组英文和数字,下面是代码。

我把这个动作拆分成了三个动作

1)播放单一的数字或字母 Play(filename) 函数
2)连续播放数字或字母 plusplay(s) 函数
3)采用PLUS(s)配合全局变量playing和breakplay,实现新播出指令到来后取消当前播放序列

由于整个功能还未实现完,所以今天就先说到这。 等完整地实现完成,看能否达到预期效果,再详细介绍。 万一失败了,我就假装国庆没加班,反正老板也没啥损失,对吧。 顺便说一句,我就是我老板,一人分饰不同角色,好处是方便进行项目管理。

function Play(filename)
    file,err=io.open("/lua/"..filename..(".mp3"))
    if (err~=nil) then 
        return
    else
        io.close(file)
    end
    audio.setStrategy(1)
    audio.play(1,"FILE","/lua/"..filename..(".mp3"),7,function() sys.publish("PLAY_"..filename.."_DONE") end)
end

--判断是否在播放中
playing=0
--是否中断当前播放
breakplay=0
function plusplay(s)
    result, data = sys.waitUntil("READY_TO_PLAY", 3000)
    playing=1
    for i=1,#s do
        filename=s:sub(i,i)
        if (filename=="*") then filename="Xing" end
        if (filename=="?") then filename="Wen" end
        if (filename=="+") then filename="Jia" end
        if (filename=="-") then filename="Jian" end
        if (filename=="#") then filename="Jing" end
        Play(filename)
        result, data = sys.waitUntil("PLAY_"..filename.."_DONE", 2000)
        if result == false then
            log.info("midemo.tts","ttsplus file play fail")
        end
        sys.wait(50)
        if (breakplay==1) then 
            log.info("midemo.tts","plusplay was broken") 
            sys.publish("READY_TO_PLAY")
            breakplay=0
            break
        end
    end
    playing=0
    sys.publish("PLAY_"..s.."_DONE")
end

function PLUS(s)
    sys.taskInit(plusplay,s)
    if (playing==0) then
        sys.publish("READY_TO_PLAY")
    else
        breakplay=1
    end
end



菜鸟学写Cat1 Demo集(三十八) TTS功能模块 2020-09-30 

八卦

美好的生活从偷懒开始,讲真,今天其实我还是多少干了点活。 比如写了一个TTS模块,但是这个模块确实是轻松加愉快。 其实写代码就是这样,同样是模块,有的也许要写一个星期还写不好,有的也许1分钟就能写好,还特别的稳定。撸代码这种事儿,其实做过的人都懂,非常难量化的。 所以为了即能偷懒又能突出我的工作绩效,我决定写一个性价比高的模块,TTS模块。 美中不足就是Air724不支持英文字母的朗读,中文和数字都是OK的,但是如果你想念B3,A4,这样的座位号就比较尴尬了,希望早日能实现吧。 朗读一个一个英文单词也许占内存太多,但是如果光是26个英文字母应该还好吧。 要么哪天我就写个补丁,其实想想也还可以,不算太夸张的难。

TTS的代码

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:TTS语音播报
-- @author 作者
-- @module midemo.tts
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-30
--------------------------------------------------------------------------
-- @使用方法
-- @使用TTS,XXX 则朗读对应的XXX,其中XXX为中文或数字,不支持英文

module(...,package.seeall)
require"audio"

--通过这个回调函数响应TTS指令
sys.subscribe("TTS",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    doc=arg[1]
    if doc==nil then doc="朗读内容为空" end
    --b=arg[2]
    --c=arg[3]
    --单次播放,音量等级7
    audio.setStrategy(1)
    audio.play(1,"TTS",doc,7)
    write("TTS:"..doc)
end)

–通过消息发送调试信息到串口和网络客户端
function write(s)
–log.info(“testUartTask.write”,s)
sys.publish(“COM”,s)
sys.publish(“NET_CMD_MONI”,s)
end

炒鸡简单吧,就酱紫,把demo添加了两三行语句就OK了

audio.setStrategy(1)
audio.play(1,"TTS",doc,7)

第一行设置冲突优先级检测,我选的是覆盖。也就是说,前面一句没读完,后面的就抢前面的话。谁最后说,谁优先级高,第二行是念出来,luat还是很简洁的吧。 简洁而不简单,这就是Luat,哦耶!
IMG_8347.MP4
菜鸟学写Cat1 Demo集(三十七) Demo集的帮助 2020-09-29

八卦

讲真,这社会人头TM活得太累了,头些年我去过日本,感觉日本人既不像电影里演的那么精明,而且性格也不凶暴。相反,我接触到的日本人看起来都傻傻的,而且特别的害羞。 为啥说这个,因为我感觉最近我周围的人有一点像日本那样了,彼此都客气起来。 特别是做生意的,你进他店里,甭管买不买东西,甭管啥态度,店家那叫一个客气,店长见了你都鞠躬,感觉就差下跪了。 觉得现在做生意可能真的是太难了。

可你要说做买卖难,问题是大家还抢着去做,你说奇怪不奇怪。

Demo集的帮助

前几天群里有个小伙伴说,你这写的啥玩意,连个帮助都没有,我一听,靠有意见。这得格外珍惜,马上改善。 所以立即给demo集增加了新功能,就是帮助功能,现在只要是在客户端(可以是手机,也可以是LLCOM串口调试助手)上输入HELP,那么demo集支持的测试指令就一览无余了。

下面是实例:

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

代码其实非常简单,使用了demo的文件读写指令,从help.md指令集中读取文本,并呈现出来就完事了。 未来可以增加一些正则表达式,然后按照参数部分的展示出来对应指令的条目。 但是我得先学会正则表达式…再等等我尽快学

菜鸟学写Cat1 Demo集(三十六) Demo集对外接口 2020-09-28

八卦

国庆了,没假期,能忙过来就不错了,不八卦了。

Demo集对外接口

今天的内容非常少,是希望您能仔细记住,这个接口以后也不会轻易更改。

作为一个开放的demo集,就算我一个人写吐血,其实也写不了几行代码,更多的我是希望能搭建一个框架,方便您也利用这个框架,编写自己的luat程序。 使用这个框架非常的简单。 事实上最简单的DEMO只有三行

这部分的作用就是订阅收到命令后的函数,您可以写任何的东西在这里面。

sys.subscribe("DEMO",function(...)

end)

我想应该没有比这更简单得了吧。 比如说串口输入DEMO,那么该程序就会被执行,在这里我们写了一行语句

write("您刚刚执行了DEMO命令".."\r\n")

这行语句的作用就是返回信息,告诉指令已经被执行了。 同样您也可以利用这条指令在任何你希望的时候对外发布信息。

所以您如果写自己的代码,就把demo.lua改个名字,然后自己写代码就够了,写完以后添加到main.lua就这么简单。完了。下面是demo.luat的源码,比简单还简单是不是?

module(...,package.seeall)

--通过这个回调函数可以响应任意的串口或网络发布的命令
sys.subscribe("DEMO",function(...)
    --通过arg可以从输入的命令行读入参数,并以逗号作为分隔符
    --a=arg[1]
    --b=arg[2]
    --c=arg[3]
    --通过write函数可以向串口和网络上报您的信息
    write("您刚刚执行了DEMO命令,".."命令共有"..#arg.."个参数".."\r\n")
end)

--通过消息发送调试信息到串口和网络客户端
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end

下面是执行结果

菜鸟学写Cat1 Demo集(连载)

代码拾取到了我们输入的串口命令,并统计了输入参数的个数。 您编写代码时,可以利用这些参数完成相对更复杂的功能

菜鸟学写Cat1 Demo集(三十五) 新增功能小结 2020-09-27

八卦

今天聊聊5G吧,先讲好,今天的八卦内容非原创,是我背地从大佬群里听贼话学舌。 让您也跟我一起接触下高端话题。 这个东西不要到外面讲,听说过于敏感,好多公众号讲这个的都被封了。

很多人习惯了2G,3G,4G 就觉得5G这个东西好像是顺理成章这么一个事儿,其实并不如此。 首先说决定通信能力的主要有几个核心技术点

1) 调制方式(这玩意主要是数学家研究的)
2) 发射效率 (半导体技术,天线技术等等)
3) 发射频谱(这就像是车道,肯定越宽走的车越多,速度越快)

在2G时代,调试方式主要是点频方式,效率非常低,因此从2G到3G普遍引入了CDMA码分多址技术的各种变种,发射的信号也从点频(类比单车道)变成了带状频谱(类比多车道),调制效率的提高,使得网速大增。 到了4G,算法的优化其实已经日趋完善,没有太大的改进空间了。 所以人家给4G又加了尾坠叫LTE,Long Term Evolution,长期演进。 说白了,这意思就是后面再革新也不改名了,就叫4G了。

其实任何技术都是如此,都有相应的瓶颈、 比如磁存储的瓶颈是信息存储密度,最起码你也得有几个原子才能保存一个bit的数据。 CPU速度的瓶颈是粒子的波动效应,电子在超高频情况下已经不在遵从经典的电学定律。 通讯技术也有自己的瓶颈,现在已经接近到达了单位带宽理论传输速率的极限了。 包括新的wifi6标准,不同类型的编码算法,其实效率都很接近。

那么5G,是啥。 我觉得5G更像是中国标准的4GLTE加强版,通过增加带宽、基站密度等非核心技术手段,提高手机的传输速率。 5G对于大国战略肯定是有非常积极的意义的,因为中国要领跑。但如果单单说物联网应用,其实适合的场景并不多。 对于当下,最经济实用的其实就是cat1,合宙出品的Air724就是cat1类型的模块,速度适中,发热低,可靠性高,满足95% 现有物联网应用场合了。 5G,目前还是测速用为主。

新功能小结

前几天一顿猛操作,增加了不少代码,这代码也没整理好,功能也没说明,以至于有人过来找我问,你写的东西都干嘛的。 那好,今天我就继续偷懒,不写新东西,总结已经完成的部分。

现在源码部分新增的文件有

mcc.lua ->这是修改过的cc.lua 源于luatask底层库,为了和稀饭老师的irtu兼容,进行了少量修改

mkey.lua->这是修改后的powerkey控制代码,源于luatask底层库,实现单按键拨号功能

NVMPara.lua->这是非易失性存储用的系统文件

onedial.lua->拨打电话的模块

修改过的系统架构图如下:

菜鸟学写Cat1 Demo集(连载)

另外,我还在gitee开源了几个小工具

Module_Print ->可以通过USB虚拟串口通讯,生成一个手机控制客户端二维码,扫描可以直接控制核心板

CS_UDP_demo ->可以通过PC本地串口或者远程Socket控制核心板的demo代码

phone_WS_demo ->H5手机控制代码,就是Module_Print二维码调用的那个H5小程序的源码

现在您可以使用以下的这些测试指令遥控开发板打电话了。

1) 串口或手机控制端发送 DIAL,XXXXXXX 直接拨打电话XXXXXX

2) 串口或手机控制端发送 PICK 接听呼入的电话

3) 串口或手机控制端发送 HUNGUP 挂断电话

4) 串口或手机控制端发送 SETCALLNUMBER,XXXXX 设置按下powerkey默认拨打电话号码为XXXXX

当然过去的测试指令也都继续兼容

5) 设置IO端口的电平 SETGPIO,io,level

说到这,demo测试指令越来越多了,我也该准备下弄个说明书了,不然我也记不住。 有位非常聪明的网友提示我可以弄一个交互式的菜单,比如打个help,就能把这些指令输出到手机或者串口,省着去翻源代码,这真的是一个非常好的主意,回来我得弄弄
菜鸟学写Cat1 Demo集(三十四) powerkey代码 2020-09-26

八卦

最近有两个人比较火他们都姓罗,在头两年,这二位可都是响彻一方的角儿。 一个叫罗永浩,一个叫罗振宇。 因为都叫老罗,我老是把他们弄混。 就像我老是分不清京东和京东方一样。

这俩人有一个共同点,都比较能忽悠。 当然有区别,前者仅仅靠忽悠挣钱, 听说要上市了。 后者除了忽悠,还造手机,欠了投资人不少钱。这就是这两年经济市场的真实写照。 凡是把钱投在实业里踏踏实实做事的,钱都没少亏。 但如果把钱都投在股市里的,现在要是股票没翻一两番,你都不好意思跟人打招呼。 如果纯粹从功利的角度来看, 似乎前者更划算。

但是我想想我那一直拿到退市华锐风电,一想到刚刚锒铛入狱的任总,我的心就安然了。 光鲜的舞台还是留给弄潮儿们吧,我身体不好,老老实实去焊板,写代码了了,我命就干这个的,我认命。

powerkey 代码详解

昨天讲到,拨打电话,我主要是通过powerkey来实现的。 众所周知,这个按钮是用来开发板开机的。 那么我怎么又能用他来控制打电话呢,这就要说powerkey库了,下面我们一起来看看。

这其实是luatask官方库里的东西,我拿来改改用了,所以第一件事首先仍然是改名,免得和原来的库冲突。

--- 模块功能:开机键功能配置
-- @module powerKey
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.06.13

require"sys"
module(..., package.seeall)

--[[
sta:按键状态,IDLE表示空闲状态,PRESSED表示已按下状态,LONGPRESSED表示已经长按下状态
longprd:长按键判断时长,默认3秒;按下大于等于3秒再弹起判定为长按键;按下后,在3秒内弹起,判定为短按键
longcb:长按键处理函数
shortcb:短按键处理函数
]]
local sta,longprd,longcb,shortcb = "IDLE",1500

local function longtimercb()
    log.info("keypad.longtimercb")
    sta = "LONGPRESSED" 
end

local function shortcb()
    log.info("keypad.shortpress")
    sys.publish("ONEDIAL")
end

local function longcb()
    log.info("keypad.longpress")
    sys.publish("HUNGUP")
end

local function keyMsg(msg)
    log.info("keyMsg",msg.key_matrix_row,msg.key_matrix_col,msg.pressed)
    if msg.pressed then
        sta = "PRESSED"
        sys.timerStart(longtimercb,longprd)
    else
        sys.timerStop(longtimercb)
        if sta=="PRESSED" then
            if shortcb then shortcb() end
        elseif sta=="LONGPRESSED" then
            (longcb or rtos.poweroff)()
        end
        sta = "IDLE"
    end
end

--- 配置开机键长按弹起和短按弹起的功能.
-- 如何定义长按键和短按键,例如长按键判断时长为3秒:
-- 按下大于等于3秒再弹起判定为长按键;
-- 按下后,在3秒内弹起,判定为短按键
-- @number[opt=3000] longPrd,长按键判断时长,单位毫秒
-- @function[opt=nil] longCb,长按弹起时的回调函数,如果为nil,使用默认的处理函数,会自动关机
-- @function[opt=nil] shortCb,短按弹起时的回调函数
-- @return nil
-- @usage
-- powerKey.setup(nil,longCb,shortCb)
-- powerKey.setup(5000,longCb)
-- powerKey.setup()
function setup(longPrd,longCb,shortCb)
    longprd,longcb,shortcb = longPrd or 3000,longCb,shortCb
end

rtos.on(rtos.MSG_KEYPAD,keyMsg)
rtos.init_module(rtos.MOD_KEYPAD,0,0,0)

代码并不长,我们可以看到这个功能通过rtos的消息回调函数完成的,再上一层就要到固件里头了。 当powerkey按下时,系统会发出 rtos.MSG_KEYPAD,这个消息,通过回调机制,触发keyMsg进行响应。 然后代码里开了一个timer,并开始计时。 当按键抬起时,根据timer计时,判断此次按下的是短按,还是长按。

讲真这个算法不好,用户更习惯的是,当按键长按一段时间后,比如3S,直接触发事件处理程序,而不是等到松开手。 等以后有空要优化一下这段逻辑。

今天周末了,出去走了走,感觉经济恢复的很快,相信电子方面的需求很快会上来,坐等大潮到来吧,希望这不是梦。

菜鸟学写Cat1 Demo集(三十三) 电话功能代码详解 2020-09-25

八卦

距离我创业的起点,有四年的时间了。 这段时光不可谓短了。人生有几个四年。 对比四年前的我,体重没变,腰围没变,白头发多了几根,笑容少了几许,其实也还算不坏。 我一直在想,如果我早一点认识合宙,早一点加入合宙社区,我的近况会不会有些变化。会不会更好一些呢。 四年前的今天,我觉得做一个消费电子产品投入个十万二十万研发费用是正常的,现在我觉得在这只要肯下功夫,花个千把块就弄出个产品也不是啥新鲜事。 这就是合宙技术社区带给我的成长。 我现在只能深鞠一躬说一声谢谢,谢谢每一个帮助我过我和正在帮助我的人。

昨天贴了代码,今天就来具体讲一讲吧

Luatask库支持电话部分的模块叫cc,文件名与模块名相同cc.lua,官方的demo叫call.lua 我的代码都是从这里七拼八凑就出来的。 通过阅读cc.lua,我们能看到拨打电话主要是通过ril指令调用AT指令实现的。

ril是一个特殊的系统指令,可以使得lua环境下调用大多数的通用AT指令,虽然不能说100%兼容,但我发现大多数指令对于ril函数都是可用的。 首先说为了打电话我们有几个先决条件。 Air724是一个带语音功能的纯4G模块,并不支持传统的线路切换方式的通话,所以如果使用通话功能,您的手机卡必须先开通Volte功能,这个一般打一下客服就可以了,新卡基本直接就支持。 移动联通电信都可以,我试过前两个效果都是很不错的,第三个我没试过,如果您试过有问题,请留言告诉我。

我在这个代码里完成了这样的功能

1) 拨打,通过网络和串口送入一个预置的电话号码,这个号码被存储在NVM中,这样掉电就不丢失了。 通过发送SETCALLNUMBER命令进行配置。 拨打电话时,调用 cc.dial(callingnumber),就可以了。

由于开发板上电路可以被Luat框架解析的只有powerkey一个,我参考powerkey.lua,进行了移植,移植后的名字叫”mkey.lua”,内容基本没啥改动,就是响应了两个事件,一个是长按,一个是短按,短按时间改为了1500ms,这个地方有一点不尽然人意的地方,松开按键的时候才会判断是否是长按,短按。所以感觉和手机按住按键不松手就动作有一些区别,感觉不是特别的方便,好处是占用系统资源少,不需要去轮询。这个得以后有机会再优化,凑合凑合得了。

功能很简单,长按就挂断,短按就接听。

2) 呼入接听, Volte都带有来电显示,来电时,通过响应 CALL_INCOMING,消息就可以得到号码 cc.accept(callingnumber),就可以接通电话了。也很简单。 接听功能和拨打功能复用powerkey按钮,短按实现。

3) 振铃,来电时,播放录制的MP3,这个就一句话,不介绍了。

audio.play(CALL,”FILE”,”/lua/mali.mp3″,audiocore.VOL7,nil,true)

为了实现不同状态下powerkey的不同功能,我们需要定义一个变量记录电话的状态:incoming=0,待机,incoming=1,来电中,incoming=2,通话中,通过响应cc模块发出的系统消息,去切换状态

4) 闪灯,闪灯我用的方法贼简单,就是GPIO定时控制的方法,闪灯这个事也不需要十分精确的控制,所以luatask自带的timer就足够了。 万一闪的有点乱也出不了大事儿对吧。

昨天晚上,我还新增加了一些功能,目的是可以从串口或者手机控制端可发出指令,原理和上面也基本一样。 我也就不啰嗦了。 总之一句话,air724 打电话是一件非常简单和愉快的事情。

--- 模块功能 一键拨号音功能
-- @author miuser
-- @module elderphone.onedial
-- @license MIT
-- @copyright miuser
-- @release 2020-09-24

require"pins"
require"common"
require "mcc"
require "audio"
module(...,package.seeall)

audio.setCallVolume(7)

local okToDial=0
local incoming=0
local onTime,offTime=0,0
--要呼出的号码
local callingnumber=""
local cnumber=""
require "NVMPara"
nvm.init("NVMPara.lua")
callingnumber=nvm.get("CALLNUMBER")

if (callingnumber=="") then
    callingnumber="117"
end

function setcallnumber(num)
    callingnumber=num
    s="CALL NUMBER="..num.."\r\n"
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
    log.info("onedial","saving call number is "..num)
    nvm.set("CALLNUMBER",num)
end
sys.subscribe("SETCALLNUMBER", setcallnumber)


--GPIO1,绿灯
local ledr = pins.setup(pio.P0_1,0)
pmd.ldoset(7,pmd.LDO_VLCD)
-- 接收LED参数配置参数
local function mblink(ontime,offtime)
    ledr(1)
    onTime=ontime
    offTime=offtime
    if offTime==0 then
        ledr(0)
    elseif onTime==0 then
        ledr(1)
    end
end

--指令拨号
local function mdial(num)
    if okToDial==1 then
        if (num==nil) then
            cnumber=callingnumber
            num=cnumber
        end
        mcc.dial(num)
        s="DIALING "..num.."\r\n"
        sys.publish("COM",s)
        sys.publish("NET_CMD_MONI",s)
    else
        s="NOT READY TO DIAL ".."\r\n"
        sys.publish("COM",s)
        sys.publish("NET_CMD_MONI",s)
    end
end
sys.subscribe("DIAL",mdial)

function answercall(msg)
    if (incoming==1) then 
        s="ANSWERING "..cnumber.."\r\n"
        mcc.accept(cnumber)
        sys.publish("COM",s)
        sys.publish("NET_CMD_MONI",s)

    else
        s="NO CALL TO ANSWER ".."\r\n"
        sys.publish("COM",s)
        sys.publish("NET_CMD_MONI",s)
    end  
end
sys.subscribe("ANSWERCALL", answercall)

function pickup()
    sys.publish("ANSWERCALL")
end
sys.subscribe("PICKUP", pickup)

--硬件按键拨号
function oktodial(msg)
    if (incoming==1) then 
        mcc.accept(cnumber)
    elseif (incoming==0) then
        if okToDial==1 then
            mcc.dial(callingnumber)
            cnumber=callingnumber
        end
    else 
        mcc.hangUp(cnumber)
    end
end

sys.subscribe("ONEDIAL", oktodial)

function hangup(msg)
    log.info("hanging up all call")
    mcc.hangUp(cnumber)
    s="HUNGUP "..cnumber.."\r\n"
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end
sys.subscribe("HUNGUP",hangup)

local function oktodial()
    sys.timerStart(function()
        okToDial=1
        mblink(0,1000)
    end,3000)
end
sys.subscribe("NET_STATE_REGISTERED", oktodial)

local function cincoming(num)
    log.info("onedial","cincoming number is "..num)
    s="INCOMING CALL "..num.."\r\n"
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
    cnumber=num
    incoming=1
    mblink(100,100)
    audio.play(CALL,"FILE","/lua/mali.mp3",audiocore.VOL7,nil,true)
end
sys.subscribe("MCALL_INCOMING",cincoming)

local function connected(num)
    cnumber=num
    incoming=2
    mblink(500,500)
end
sys.subscribe("MCALL_CONNECTED",connected)

local function disconnected(num)
    incoming=0
    mblink(0,1000)
    audio.stop()
    cnumber=callingnumber
    s="DISCONNECTED "..cnumber.."\r\n"
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end
sys.subscribe("MCALL_DISCONNECTED",disconnected)






sys.taskInit(function()
    local oncount,offcount=0,0
    local stage="on"
    while true do
        if onTime~=0 and offTime~=0 then 
            if stage=="on" then
                if oncount<(onTime/20) then
                    ledr(0)

                    oncount=oncount+1
                    --log.info("elderphone.light","turn on all three led",oncount)
                else
                    oncount=0
                    stage="off"
                    --log.info("elderphone.light","stage=off")
                end
            end
            if stage=="off" then 
                if offcount<(offTime/20) then
                    ledr(1)         
                    offcount=offcount+1
                    --log.info("elderphone.light","turn off all three led",offcount)
                else
                    offcount=0
                    stage="on"
                    --log.info("elderphone.light","stage=on")
                end
            end
        end
        sys.wait(20)
    end
end
)

完成后的代码,可以实现:

1) 串口或手机控制端发送 DIAL,XXXXXXX 直接拨打电话XXXXXX

2) 串口或手机控制端发送 PICK 接听呼入的电话

3) 串口或手机控制端发送 HUNGUP 挂断电话

4) 串口或手机控制端发送 SETCALLNUMBER,XXXXX 设置按下powerkey默认拨打电话号码为XXXXX

代码已经更新到gitee,您可以随时下载验证,有问题告诉我哦

https://gitee.com/miuser00/midemo

菜鸟学写Cat1 Demo集(三二) 增加电话功能 2020-09-24

八卦

几天算是临时起意改变了原有计划。 最近一直在研究怎么能把开发板的用户群稍微增加一点。 所以就琢磨来琢磨去,发现自己迷糊的一塌糊涂。 作为一个电话核心板,竟然到现在都不支持电话功能。 这真是脑部充了水。 所以一点也没多想,赶紧增加一个电话模块,好说歹说,也先把打电话功能弄上才配的上电话核心板这名字。

但是道路是曲折的,我发现电话的luatask库cc似乎与irtu存在一些小的兼容性问题。 只要加载cc,程序就会死机。 所以花了一点篇幅,修改了cc库的内容,使他和irtu固件相互兼容,当然,名字也得换换,在前面加上了一个字头m,这样就不会发生库调用混乱了。

在修改的过程当中,我当然免不了要阅读luatask的源码。 我惊讶的发现,我现在所谓的新思路,其实是稀饭老师在N年前就已经想到的。 “模块之间通过消息传递”,“不使用公有变量,通过消息方式对模块状态进行转换” 可以说,没有一丝一毫算是人家没想到的。

这就让我想到了,我在以前就意识到的一个问题,也许创新就是一个伪命题。 人类社会存在了那么多年,社会方面人与人的关系其实本质上没有太大的变化,所以我们今天的问题,过去N久前人家肯定都遇到过,所以读史其实完全可以99.9%,通晓今天发生的这些事。 论到信息技术,这确实是一个比较新的东西,但是西方起码也是领跑了好几十年,就算中国人比外国人都聪明一点,那也不至于说我们今天想到的事,过去几十年外国人都躺着睡大觉想不到。 一想到这里,我恍然大悟,其实所谓创新其实是一个伪命题,再赶上别人的步调之前,好好模仿学习才是捷径。 妄图通过自己一点点的知识,就搞创新,这是人家的笑柄而已,老老实实的邯郸学步才是正路。 学不会走路,还学不会颠么,哼!

今天继续偷懒贴代码,解释容后,今天确实体力不支

--- 模块功能 一键拨号音功能
-- @author miuser
-- @module elderphone.onedial
-- @license MIT
-- @copyright miuser
-- @release 2020-09-24

require"pins"
require"common"
require "mcc"
require "audio"
module(...,package.seeall)

audio.setCallVolume(7)

local okToDial=0
local incoming=0
local onTime,offTime=0,0
local callingnumber="13821766831"

function setcallnumber(num)
    callingnumber=num
    s="CALL NUMBER="..num
    sys.publish("COM",s)
    sys.publish("NET_CMD_MONI",s)
end
sys.subscribe("SETCALLNUMBER", setcallnumber)


--GPIO1,绿灯
local ledr = pins.setup(pio.P0_1,0)
pmd.ldoset(7,pmd.LDO_VLCD)
-- 接收LED参数配置参数
local function mblink(ontime,offtime)
    ledr(1)
    onTime=ontime
    offTime=offtime
    if offTime==0 then
        ledr(0)
    elseif onTime==0 then
        ledr(1)
    end
end


function oktodial(msg)
    log.info("Call incoming","incoming=",incoming)
    if (incoming==1) then 
        mcc.accept(callingnumber)
    elseif (incoming==0) then
        if okToDial==1 then
            mcc.dial(callingnumber)
        end
    else 
        mcc.hangUp(callingnumber)
    end
end

sys.subscribe("ONEDIAL", oktodial)

function hangup(msg)
    log.info("hanging up all call")
    mcc.hangUp(callingnumber)
end
sys.subscribe("HUNGUP",hangup)

local function oktodial()
    sys.timerStart(function()
        okToDial=1
        mblink(0,1000)
    end,3000)
end
sys.subscribe("NET_STATE_REGISTERED", oktodial)

local function cincoming(num)
    log.info("cincoming")
    incoming=1
    mblink(100,100)
end
sys.subscribe("MCALL_INCOMING",cincoming)

local function connected(num)
    incoming=2
    mblink(500,500)
end
sys.subscribe("MCALL_CONNECTED",connected)

local function disconnected(num)
    incoming=0
    mblink(0,1000)
end
sys.subscribe("MCALL_DISCONNECTED",disconnected)





sys.taskInit(function()
    local oncount,offcount=0,0
    local stage="on"
    while true do
        if onTime~=0 and offTime~=0 then 
            if stage=="on" then
                if oncount<(onTime/20) then
                    ledr(0)

                    oncount=oncount+1
                    --log.info("elderphone.light","turn on all three led",oncount)
                else
                    oncount=0
                    stage="off"
                    --log.info("elderphone.light","stage=off")
                end
            end
            if stage=="off" then 
                if offcount<(offTime/20) then
                    ledr(1)         
                    offcount=offcount+1
                    --log.info("elderphone.light","turn off all three led",offcount)
                else
                    offcount=0
                    stage="on"
                    --log.info("elderphone.light","stage=on")
                end
            end
        end
        sys.wait(20)
    end
end
)



菜鸟学写Cat1 Demo集(三十二) 逻辑优化 2020-09-23

八卦

本来今天的题目想写的是小结。但刚刚开始测试了一下就发现了不少小问题。所以先延长两天进行逻辑优化。 几天没啥可八卦的,由于媒体的过渡发达,现在消息的输出口径越来越集中化,传递的速度基本上也是秒达。 所以与其说我们的消息越来越发达,在某种意义上说,我们生活也变得越来越闭塞。 看着同样的电影,读着同样的新闻,做着差不多的事儿。 如果说唯一可以有一点不同,大概就是对事情各自的想法可能不太一样。 不过这其实也不重要,因为你该干啥还得干啥。 最终一个人的收获还是基本和产出成正比的。 高劳动产出比比的项目都写在刑法里呢… 用老话说,背着抱着一般沉。 写完文章洗洗睡才是正路。

重读目标

我们在第一章定做过了需求分析,我们来一步一步看看完成情况

1) 模块启动后,通知我一切准备就绪

完成情况:目前可以通过显示屏回显信号质量,upws联网情况,但没有从串口回送这个信息。

2) 我告诉模块去连接哪的服务器,给他鉴权信息

目前只能连接upws服务器,但多数用户对我写的自有协议并不感冒,更多的用户希望使用诸如EMQX,Onenet、阿里云、腾讯云之类的服务器,这部分我们还没做,而且upws服务器配置方式也不对,目前只支持源码级别的配置

3) 他帮我联网,然后维护连接。

连接是可以自动完成,但服务器出问题,并没有维护机制进行重连

4) 给模块发点指令,并立即执行,比如:

通过总线读取硬件数据 ->尚未实现

通过总线控制硬件 ->尚未实现

控制GPIO输出 ->实现

读取GPIO输入 ->实现

屏幕显示内容 ->目前只支持128X32 OLED英文显示

扬声器发声 ->未实现

获取系统时间 ->未实现

5) 定时执行指令

未实现

6) 模块系统指令

未实现

进度情况

一个月过去了,1-6这几个需求,基本上一个都没实现… 不过没关系,按照我原有的计划,时间进度不在考量的范围对吧。 但是我记得有一些东西是要做到的。 一、完备的文档和代码说明 二、代码必须高度稳定 三、测试和主代码同期完成。

现有功能测试

为了达成代码同期测试的目的,我开发了两个小工具,和手机适配代码。并用这些工具进行压力测试。 并发现了一些问题,记录如下:

1) 当手机客户端发送消息过快的时候,服务端回声消除机制不稳定,失效导致消息发生回授的情况。

2) 当模块主导发送数据时,现有逻辑不理想,应该是服务端收到消息后返回->OK标志,但现有机制无论发送是否成功,都会返回OK

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

下一步

所以后面优先完成如下内容:

1) 修正发现的回声bug

2) 完成第一个需求,增设串口回送设备准备好的消息

3) 新增其他各种云的通讯接口,逐步测试发挥iRTU应有的功能。

同时开始整理代码里面的注释,详细叙述测试方法和测试逻辑,这些事恐怕没一个星期做不完… 感觉写代码比开发硬件慢多了

菜鸟学写Cat1 Demo集(三一) 故障排除 2020-09-22

八卦

经过了N天的纠结编码的奔溃问题解决了。 我这先留个扣子,后面讲问题到底出在哪。 我今天有感而发,想谈谈误码率的问题。 我年轻的时候喜欢玩CPU,那时候我的梦想是拥有一块奔腾的处理器,特别是带金属顶壳的那种,因为,比较容易超频。 陶瓷的就差一点,因为基本没差超频空间。

菜鸟学写Cat1 Demo集(连载)

但是无论体质再好的CPU,超频都有极限,比如这块多能处理器,他的极限频率我记得大概是能到233MHz,可以流畅的打红色警戒1。 当频率再继续上升的时候,并不是说系统就一定不能运行,而是出错的几率会大大的上升。 动不动就蓝屏死机。 当然看这文章的都是行家里手,这你们也都玩过,对吧。 显然,蓝屏死机一定是某个指令在执行的过程中发生了错误,但郁闷的是我们并无法提前预知是哪个晶体管在执行哪条指令的时候出的错。 最多能知道是哪个电路模块出的错就很不易了。

其实我是想说,人的工作也是一样,都有一个阈值,当劳动强度高于这个阈值的时候,错误率就会上升,这时候采用纠错以及错误恢复机制往往也是无效的,因为错误过于的随机,无知道发生在哪,所以也就没法进行有的放矢的纠正。 所以我说,人该吃吃,该喝喝,天塌下来砸大家。 使蛮力,没啥用,没准把自己搞死机了,重启一遍,效率更吃亏。 所以写完这篇文章,我就滚去睡觉。 这绝对不是给自己找借口偷懒,这绝对不是给自己找借口偷懒,这绝对不是给自己找借口偷懒,我休息是为了更好的工作!

解Bug

这次的bug藏得非常深,我花了大约6个小时才揪出来,是的,真的就是花了这么多时间。 问题很简单,因为我在目录里放了一个空文件叫issuelist.txt。 我发现源代码目录里存在长度为0的空文件,其余的文件读写就会有问题,这应该算是luatools的一个bug,但是是可以避免的。 说说我这个弯路怎么走的,以及以后预防的方法吧,虽然其实错误很随机也没法预防。

#Bug的产生

首先说,我的代码是不能正常运行的,而且是改代码过程中产生的。当时我只是顺手建了一个issuelist,防备忘记一些已知的bug,想当然的认为这和代码是无关的,所以没放在心上,也没记住这个操作。 我想当然认为是代码编写错误,报的错误是无法加载irtu.json文件。 所我花了大把的时间反复的修改这个文件,都没有任何效果。 我又考虑可能是加载这个文件的代码有问题,所以我又一遍一遍的改,当然也是无效的。 最后我已经开始怀疑到文件缓存、底层固件没擦除干净等各种可能的原因…

Bug的发现

最终我发现一切策略失效的时候,我采用了以下的方法,最终定位了错误。

1) 找到一个较早期的可用的版本。 把新版的文件一个一个的覆盖老版本的文件,由于我的代码才用的洋葱结构,所以新代码基本上是兼容老代码的,覆盖以后上传看是否报错。

    经过一番测试,仍然报错

2) 将老的版本的所有源文件,拷贝到新的目录里

    仍然报错

经过这些比对,我已经基本把问题定位到了文件系统上了,不然试验2应该是可以执行的,因为代码逻辑完全是调通的,然后就用luatools,一个一个的排除文件,直到拿掉issuelist.txt,故障消失了,问题找到。

经验总结

用近期好的代码和出问题的代码进行交叉比对是发现问题的捷径。 写代码是这样,硬件分析也是,这个有个学名叫A-B-A Test,在实践中比啥理论分析都好使。

新增代码逻辑

经过了这几天的努力,我小范围重构了网络部分的逻辑。改动如下

1) 将upws连接服务器的代码从iRTU适配器的代码中剥离出来

2) 放弃使用iRTU自带的数据流模板功能,在irtu.lua中重新编写数据流转换代码

3) upws服务器本身自带消息回声功能(即将UDP端口的数据包原封不动抄送回发送端),编写消除回音的代码,防止收发形成正循环反馈。

4) 增加延迟发送队列,将过快的指令按每100ms发送一包的速度进行缓存,防止发送出现丢失,或者粘包。

5) 增加了设备心跳包,心跳包为JSON格式,里面包含了核心板的IMEI,SN,ID,MM,位置,信号强度等数据,每10秒钟发送一次

完成后对外暴露的框架消息接口如下

1) 网络命令收发,即可以被解析的字符串:

发送到网络 NET_CMD_MONI

网络接收 NET_CMD_MINO

2) 网络数据透传,发送的数据直接抄送,不作处理

发送到网络 NET_RAW_MONI

网络接收 NET_RAW_MINO

建议使用BASE64编码,发送的每包数据,头部和尾部不能包含回车或空格,否则多余的回车或空格会被系统忽略掉。

3)串口命令收发

发送到串口 COM

串口接收 COMRSV

4)显示接口

LED屏幕显示 DISPLAY,最长为21字符

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected],如有侵权嫌疑将立即纠正
---------------------------------------------------------------------------------------------------------------------------------------
--- 模块功能:upws服务器适配
-- @author miuser
-- @module midemo.upws
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-014
--------------------------------------------------------------------------
-- @说明部分
--------------------------------------------------------------------------
require "net"
require "misc"
require "common"
module(...,package.seeall)

P13Level=0
sendPool={}

function gb(str)
    return common.utf8ToGb2312(str)
end

--回报状态信息
function ReportStatus()
    rssi=net.getRssi()
    tm = misc.getClock()
    ret=
    {
        --核心板IMEI
        ID=bs.IMEI,
        --核心板SN
        SN=bs.SN,
        --核心板ID
        ID=bs.ID,
        --核心板MM
        MM=bs.MM,
        --网络状态
        NETSTATUS=create.getDatalink(),
        --基站定位成功
        isLocated=bs.isLocated,   
        --经度
        LONGITUDE=bs.LONGITUDE,
        --维度
        LATITUDE=bs.LATITUDE,      
        --设备端时间戳
        TIME=tm,
        --信号强度
        RSSI=rssi,
        --P13
        P13=P13Level    
    }
    dat=json.encode(ret)
    sys.publish("NET_HEART_MONI",dat)
end

--服务器连接成功后定时发送心跳数据包
sys.taskInit(function()
    while (NETSTATUS==false) do 
        sys.wait(1000)
    end
    while true do      
        ReportStatus()  
        --每10S发送一次心跳
        sys.wait(10000)
    end
end)

function GPIOChange(pin,level)
    log.info("upws","GPIOChange",level)
    if pin==13 then P13Level=level end
end
sys.subscribe("GPIO_LEVEL_CHANGE",GPIOChange)



sys.subscribe("NET_RECV_WAIT_1",function(uid,msg)

    local echo=0
    log.info("upws","socket received:"..msg) 
    --消除服务器抄送的发送数据
    table.foreach(sendPool,function(i,v) 
        if v==msg then
            log.info("upws","cancelling message:"..v) 
            table.remove(sendPool,i)
            echo=1
        end 
    end)
    --服务器会将收到的报文进行抄送回报给UDP发送端,此处进行处理滤除掉
    --设置最多20个缓冲,超出则保留最后的20个消息
    log.info("upws","sendPool len:"..#sendPool)
    todelete=#sendPool-20
    for i=1,todelete do
        table.remove(sendPool,i)
    end
    if (echo==1) then return end
    local str=msg:sub(40,-3)
    log.info("upws","received:"..str) 
    --透传
    if (msg:sub(7,7)=="C") then 
        sys.publish("NET_RAW_MINO",str)
        log.info("upws","Received in RAW "..str)
    --心跳包
    elseif (msg:sub(7,7)=="B") then 
        sys.publish("NET_BEAT_MINO",str) 
        log.info("upws","Received in BEAT "..str)
    --命令
    elseif (msg:sub(7,7)=="A") then 
        sys.publish("NET_CMD_MINO",str)
        log.info("upws","Received in CMD "..str)
         -- 串口的数据读完后清空缓冲区
        local splitlist = {}
        string.gsub(str, '[^,]+', function(w) table.insert(splitlist, w) end)
        local count=table.getn(splitlist)
        --sys.publish("UARTIN",str)
        for i=2,#splitlist do 
            splitlist[i]=common.gb2312ToUtf8(splitlist[i])
            splitlist[i]=bs.PIN_MAP(splitlist[i])
        end
        splitlist[1]=bs.Trim_CMD(splitlist[1]) 
        sys.publish(unpack(splitlist))
        sendQueue = {}
    else       
        return
    end

end)

sys.subscribe("NET_RAW_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."C".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT",ret)
    table.insert(sendPool,ret)
    log.info("upws","NET_CMD_MONI:"..ret)
    log.info("upws","sendPool len:"..#sendPool)
    rtos.sleep(100) 
end)

sys.subscribe("NET_HEART_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."B".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT",ret)
    table.insert(sendPool,ret)
    log.info("upws","NET_CMD_MONI:"..ret)
    log.info("upws","sendPool len:"..#sendPool)
    rtos.sleep(100) 
end)

sys.subscribe("NET_CMD_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."A".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT",ret)
    table.insert(sendPool,ret)
    log.info("upws","NET_CMD_MONI:"..ret) 
    log.info("upws","sendPool len:"..#sendPool)
    rtos.sleep(100) 
end)

sys.subscribe("NET_RAW_MINO", function(str)
    sys.publish("DISPLAY",str)
    sys.publish("NET_RAW_MONI",bs.Trim_CMD(str).."->OK".."\r\n")
    sys.publish("COM",str.."\n\r")
end)

sys.subscribe("NET_CMD_MINO", function(str)
    sys.publish("DISPLAY",str)
    sys.publish("NET_CMD_MONI",bs.Trim_CMD(str).."->OK".."\r\n")
    sys.publish("COM",str.."\n\r")
end)



--对网络送出的内容进行缓冲,每隔100ms发送一次
sendBuff={}
sys.subscribe("NET_SENT", function(str)
    table.insert(sendBuff, str)
end)

sys.timerLoopStart(function()
    if (#sendBuff>0) then
        sys.publish("NET_SENT_RDY_1",table.remove(sendBuff,1))
    end
end,100)

这是修改调通的upws通讯模块

完成的功能也比较简单,就是稍微絮叨了点。

1)组合心跳包数据,并定时发送

2) 服务器回声包消除程序,滤掉与发送数据相同的指令或数据

3) 根据upws数据收发的格式进行数据打包,解包操作

您非愿意看,我也不反对,反正我自己写代码,我都不太乐意看。 gitee上的代码已经更新完了,建议您直接下载烧进去得了。今天就到这里了,晚安。

菜鸟学写Cat1 Demo集(三十) 标签 2020-09-21

八卦

幻想是每个人都有的东西,比如我每天都在幻想。 幻想一夜暴富,幻想出人头地。

其实真相就在眼前,小的时候都学过最简单的物理定律,S=vt 想要在财富的道路上奔跑,有两个因素,第一个是v,然后才是t, V就是你跑的速度,很多人起点很低,比如我,那么说要取得进步肯定就比别人难得多。 因为无论在知识积累、社会资源、层次认知上和上层人都有差距。 那么后天努力呢,我们把努力当做加速度来看 v=at, 能力的提高同样花费时间。 如果初始v很低,t很小,a这玩意基本作用就不大了,但我很缺t这东西,毕竟岁数不小了,还是洗洗睡了吧。

菜鸟学写Cat1 Demo集(连载)

好在我非常的Q,我总觉得位置低一点其实挺好的。 人的幸福程度其实与自己所处的位置关系不大。 我举个例子,一个人出生在豪门,那他一出生就拥有一切,他只会觉得这一切理所应当。 打比方说某人说要是因为自己能喘气而感恩,你肯定认为他是神经病。 财富是一样的,出生就带着的东西并不会带来幸福。 但自身位置的提高,特别是靠自己的拼搏使生活变得越来越好确实会让人感到幸福。

但努力很累,这时候商机就来了! 每个人都渴望靠较小的代价,获取较多的利益,比如我感冒了,我就喜欢花一元钱,去医院买一片药,然后吃下去,病立即就好了。 所以市场上充斥着各种各样的商品,都贴上了标签,标签的作用就是告诉你给我点小钱,就能立即满足你的需求。

有些东西,比较清晰,比如馒头,不用贴标,也知道是用来吃的。 但是复杂的就比较好骗人,比如健康。 食品贴上健康的标签,就可以涨价。 至于吃了是不是让人真的健康,没人知道。 聪明也是,儿童的产品贴上能让人变聪明的标签,也能多卖钱。 至于长大了孩子到底有没有变聪明,这个也没人知道。

总之,万物,只要贴上了恰当的标签,就可以激发人们的购物欲。 提别是那些虚无缥缈的,更容易让人浮想联翩的最好。 健康,美貌,成功,欢乐是一切的商品的落脚点。 做生意必须奔着人们的刚需来,如果不能满足,起码也要假装能满足。 不然这东西就卖不出去,

作为一个梦想一夜暴富的奸商,我肯定是要利用这个心理学效应,所以我要给我的商品贴上标签。

做标签

(大家都知道,我昨天崩溃了,今天一直在修复代码。 所以我只能歪楼谈别的。 )

我的标签期望是满足人的控制欲,再具体点说:我期望我能让您通过扫一个标签控制核心板。 (大家不要喷我,复杂的欲望我满足不了)

我很懒,讲真。 我一直对现在的物联网产品的复杂使用深恶痛绝,扫码,配网,关联平台,想想就头大。 我期望的是,扫一扫直接控制硬件,那么我首要解决的问题就是如何打码,因为每个设备的码肯定不能相同,不然如何区分不同的设备呢。 如果码相同又得去配网,关联平台,注册设备…我这智商确实搞不定。 所以我选简单的,一机一码。

那问题来了,现有的标签打印机自带的程序基本上都是编辑一个图样,这样效率太低。 所以我只好自己弄了一个标签打印程序,还好不太麻烦。 代码我也开源了 https://gitee.com/miuser00/midemo/tree/master/tools
代码是用C#写的,可以根据从模块里读出的信息,打印出来对应的标签。 因为这个项目偏离我们的主题太远,我就不展开了,不然题目就得改成菜鸟学写C#打标签程序了…这肯定不行。

下面我就讲讲这个东东的使用方法吧,您可以直接拿来用就行了。首先您需要买一台佳博的USB标签打印机,推荐用GP-3120TU。然后连上Phone core 核心板,就可以打印了,非常简单。 设置都是自动化的,您也可以微调下打印位置,一点都不难。这个程序经过了两年多的修修补补,实用性已经很高了。
菜鸟学写Cat1 Demo集(连载)

好了,今天就酱紫了,看看我标签的效果吧,我还是挺沾沾自喜的,这也算是花样点灯吧

菜鸟学写Cat1 Demo集(连载)
菜鸟学写Cat1 Demo集(连载)

标签有点模糊,凑合能用就行了,是吧。

菜鸟学写Cat1 Demo集(二十九) 代码崩溃 2020-09-20

八卦

上海合宙社区有一个读书会,原来限定是每个月读一本书,没有完成任务的人,就要发200元的红包给完成任务的人,我每个月都能拿到红包。 最近由于Cat1业务火爆,这个活动被暂停了,不然努力工作的兄弟就太吃亏了。 我不一样,我这人汤泡饭,无论有没有读书会活动,我都会读书。 一般来说一个月也是大概一本书的样子。 我主要用来在临睡前打发时间,不看几眼书,放松不下来,睡不好觉。

最近我在看的一本书是霍金写的《大设计》,这书虽然是现代物理科普书,但是知识层次较比我的认知水平还是高了一大截,所以我就不乱讲了。我只提一个我印象深刻的结论。 这个世界存在,因为存在着一套理论来描述这个世界。 你可以感受到,这个说法其实听起来已经非常的唯心了。 这本书,从头至尾,都在讨论,我们的世界为什么会是现在的这个样子,如果各种物理学的参数稍微有一点点偏移,这个世界就不会是现在这样子。

我在说啥…我在说。 我写的代码全崩溃了。 且没有备份,是的没有备份!!! 我彻底的崩溃了。 今天真的没有作业可以交。

翻车原因

我今天就贴贴我翻车的原因,说来话长,这并不是一天的事。 这要从内置iRTU固件说起。 首先说iRTU固件是稀饭老师倾力制作的透传固件,我用这个核心来完成demo集和upws服务器的通讯使用。 但是由于缺乏对iRTU固件的透彻理解,在进行封装和剥离的时候,对于很多技术细节,比如数据端口的映射、数据包解析,上电加载的参数时序方面都缺乏经验,导致这个固件内置的相当勉强,虽然成功了,但体力已经严重透支,且代码质量不高。 但是维持日更,我做了如下的事。

一方面我极力的开新的任务编写upws服务器连接代码维持日更内容。 另一方面,不断地修改优化iRTU的封装代码。 同时upws的代码调试由于牵涉到上位机的调试,又需要编写h5代码,和C#代码,因此我同时要四部分代码的进行同步修改。 最终导致了恶劣的结果

1) 原有mi-demo固件变得完全不可用,功能完全失效,且不知问题在哪。

2) 通讯逻辑,一日三变,代码破碎,结构混乱。

3) 所有代码中间修改过程既没有上传gitee,又没有本地备份,导致手头没有任何中间成果的代码版本。

所以今天能做的只有一件事,彻底停下来,如果有必要,甚至不排除明天停更一天,修养身体,再回来找问题。

结论

心急吃不了热豆腐,赶工=自毁

菜鸟学写Cat1 Demo集(二十八) upws适配代码 2020-09-19

八卦

今天我听到的最邪乎的消息是要对台动武,国家大事少掺和好。所以我不评论。不过我拥护祖国统一,中国人都有家的情怀,这是根深蒂固的。

upws云服务

作为本开源项目的一个延伸,upws服务器是我当下要首先尝试支持的。 我过去的老板对我的评价,说我这个人比较独,说实话,我现在越来越觉得我过去的老板一针见血。 除非万不得已,我一般不愿意用别人的东西。 除非是别人写的我实在搞不定,比如iRTU。 向云服务这个东东,就比较简单了,我自己随随便便就写了,协议本身也不复杂,玩具级的。 我个人信奉的是60分万岁,61分浪费。够用完事。

http://box.miuser.net/MTJMCN/MTCP.html

这是2G写的协议,现在换了4G,按说很多地方应该修改,但是结构上并没有啥需要调整到地方,除了4G性能更好,所以先贴在这您做个参考。

upws适配代码

今天又是凭代码混饭吃的,一天,因为这周末的确太忙,太累了。 代码的内容,容后再详细讲。

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected],如有侵权嫌疑将立即纠正
---------------------------------------------------------------------------------------------------------------------------------------
--- 模块功能:upws服务器适配
-- @author miuser
-- @module midemo.upws
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-014
--------------------------------------------------------------------------
-- @说明部分
--------------------------------------------------------------------------
require "net"
require "misc"
require "common"
module(...,package.seeall)

function gb(str)
    return common.utf8ToGb2312(str)
end

--回报状态信息
function ReportStatus()
    rssi=net.getRssi()
    tm = misc.getClock()
    ret=
    {
        --核心板IMEI
        ID=bs.IMEI,
        --核心板SN
        SN=bs.SN,
        --核心板ID
        ID=bs.ID,
        --核心板MM
        MM=bs.MM,
        --网络状态
        NETSTATUS=create.getDatalink(),
        --基站定位成功
        isLocated=bs.isLocated,   
        --经度
        LONGITUDE=bs.LONGITUDE,
        --维度
        LATITUDE=bs.LATITUDE,      
        --设备端时间戳
        TIME=tm,
        --信号强度
        RSSI=rssi,    
    }
    dat=json.encode(ret)
    sys.publish("NET_HEART_MONI",dat)
end

--服务器连接成功后定时发送心跳数据包
sys.taskInit(function()
    while (NETSTATUS==false) do 
        sys.wait(1000)
    end
    while true do      
        ReportStatus()  
        --每10S发送一次心跳
        sys.wait(10000)
    end
end)

经过了反复斟酌,我的iRTU适配代码也有所改变,一起贴上占字数

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:iRTU固件执行前适配
-- @author miuser
-- @module midemo.irtu_init
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-08
--------------------------------------------------------------------------
------------------------iRTU配置信息---------------------------------
--通过修改当前目录的irtu.json,可以对iRTU模块初始状态进行配置
--通过create.getDatalink()函数获取iRTU服务器连接状态 true为已经连接
--通过sys.publish("NET_RAW_MONI",str)向服务器发送数据
--通过 sys.subscribe("NET_RAW_MONI", function(msg) ...end) 接收服务器发送来的数据

--------------------------------------------------------------------------

require "bs"
--以下部分的代码无须用户修改
module(..., package.seeall)

--_G.PRODUCT_KEY = "DPVrZXiffhEUBeHOUwOKTlESam3aXvnR"
--iRTU的缺省配置文件,描述了首次烧写后的iRTU行为
CONFIG="irtu.json"

local function readfile(filename)--打开指定文件并输出内容

    local filehandle=io.open(filename,"r")--第一个参数是文件名,第二个是打开方式,'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then          --判断文件是否存在
        local fileval=filehandle:read("*all")--读出文件内容
      if  fileval  then
           print(fileval)  --如果文件存在,打印文件内容
           filehandle:close()--关闭文件
           return fileval
      else 
           print("The file is empty")--文件不存在
      end
    else 
        print("文件不存在或文件输入格式不正确") --打开失败  
    end    
end


local function writevalw(filename,value)--在指定文件中添加内容
    local filehandle = io.open(filename,"w")--第一个参数是文件名,后一个是打开模式'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then
        filehandle:write(value)--写入要写入的内容
        filehandle:close()
    else
        log.info("midemo.irtu_adapter","irtu适配文件不存在或文件输入格式不正确") 
    end
end
CONFIG="/lua/"..CONFIG

content=readfile(CONFIG)
config=json.decode(content)
content_modified=json.encode(config)
log.info("updated json",content_modified)
log.info("bs.ID is ",bs.ID)
log.info("bs.MM is ",bs.MM)
writevalw("/CONFIG.cnf",content_modified)


sys.subscribe("NET_RECV_WAIT_1",function(uid,msg)
    local str =msg
    --透传
    if (str:sub(7,7)=="C") then 
        str=str:sub(40,-3)
        sys.publish("DISPLAY",str)
        sys.publish("NET_RAW_MINO",str)
        sys.publish("COM",str.."->OK".."\n\r")
    --心跳包
    elseif (str:sub(7,7)=="B") then 
        str=str:sub(40,-3)
        --sys.publish("DISPLAY",str)
        sys.publish("NET_BEAT_MINO",str)
        --sys.publish("COM",str.."->OK".."\n\r")    
    --命令
    elseif (str:sub(7,7)=="A") then 
        str=str:sub(40,-3)
        sys.publish("DISPLAY",str)
        sys.publish("NET_CMD_MINO",str)
        sys.publish("COM",str.."->OK".."\n\r")
    else       
        return
    end
    -- 串口的数据读完后清空缓冲区
    local splitlist = {}
    string.gsub(str, '[^,]+', function(w) table.insert(splitlist, w) end)
    local count=table.getn(splitlist)
    --sys.publish("UARTIN",str)
    for i=1,#splitlist do 
        splitlist[i]=common.gb2312ToUtf8(splitlist[i])
        splitlist[i]=bs.PIN_MAP(splitlist[i])
    end 
    sys.publish(unpack(splitlist))
    sendQueue = {}

end)

sys.subscribe("NET_RAW_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."C".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT_RDY_1",ret)
end)

sys.subscribe("NET_HEART_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."B".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT_RDY_1",ret)
end)

sys.subscribe("NET_CMD_MONI", function(str)
    totallen=41+str:len()
    local ret=string.format("%04d",totallen).."01".."A".."01"..""..bs.ID..""..""..bs.MM.."".."1234"..str.."05"
    sys.publish("NET_SENT_RDY_1",ret)
end)

其实一点都不难,都是一些内容不断的重复。 这是我另一位师傅教我的,简单的事情重复做,奇迹不知不觉就发生了

菜鸟学写Cat1 Demo集(二十七)调试工具 (2020-09-18)

八卦

人越累,工作效率越低。 工作越散碎,头脑的切换成本越高。 因此分工导致高效,啥事儿都自己一个人弄就得累死。 今天累死了,不八卦了。

调试工具

今天一直在调试iRTU和midemo的适配,但是发现困难重重,主要问题是在上下行数据流模板这块不清晰。 因为需要对三种类型的数据包进行分别解析, 而调试这部分代码,有没有现成的环境可以来做。 因为要配置的是一段嵌在json字符串里面的lua代码,非常头疼。 另外数据的收发情况也不清楚, 所以马上的翻出了一个以前写的UDP C# 调试工具,并做了下适配,就这个样子

菜鸟学写Cat1 Demo集(连载)

这样子至少能看到到我发出去和收进来的包格式是啥样…

今天把源码给出来,这就算我交不上作业给各位大伙赔罪吧。 代码是C#写的,可以用VS2019打开,直接使用。下载地址为:
http://file.miuser.net/pool/CS_UDP_demo.rar

菜鸟学写Cat1 Demo集(二十六) 重撸消息 2020-09-17

八卦

第八届哈姆丹国际摄影大赛在迪拜歌剧院刚刚揭晓了获奖名单。就下面这幅照片赢得了冠军,得了80万人民币的奖金, 够我喝每天喝掺水的可口可乐六百多年,就算不掺水也够喝整整三生三世… 但是我必须指出,后来这个事儿有人爆料,这个照片是摆拍的! 虽然这并不违反大赛规则。
菜鸟学写Cat1 Demo集(连载)
我想说的其实是有关于社会分工。 我们生在一个系统化程度很高的社会,几乎社会的方方面面都是系统化的,专业化的。 专业的老师,专业的工程师这都不新鲜,关键是乞丐、流浪汉、拾荒者、这些也都是专业的。 由于众所周知的一万小时定律, 在任何一个可以容身的领域,无一例外,业余的都干不过专业的。 所以如果你真的很困难,就出去乞讨,大抵是挣不到钱的,因为你会分分钟被职业乞丐秒。

同样,撂地摆摊做小买卖也一种很专业的职业技能。 很多有点钱的人往往会愚蠢的认为,自己只是拉不下脸,如果真到了那个份儿上,撂地谋生轻而易举,我想说这真是太天真了。 当然这样的人是少数,我一个都不认识,我说的都是社会上的少数人,而这些人我恰好一个都不熟。

其实我想说的是,中华民族近代作为一个灾难深重的民族,每一个能祖祖辈辈在自己的家园生存下来的都得是有真本事的,就算不会降龙十巴掌,起码也是个练家子。 出国走的那些都没留下的有本事。 对吧,但凡混得好,谁愿意背井离乡,去西方所谓的享福。 中国人的故乡情结还是很重的。

消息指令

我们昨天撸了撸各个代码模块,今天继续撸消息指令。 对于本demo集,与官方demo集最大的不同就是支持统一的测试接口,也就是可以通过串口发送命令对各个模块进行遥控。 而且各个模块之间也可以通过消息进行通讯。 通过这种机制,我们即使各个模块能够彼此协作,又免除了变量级别的耦合,当我们不需要使用某个模块时,从代码移除掉,并不会妨害到其他的功能。

下面我简要的把各个模块之间的消息做一汇总,回来一并增加到模块的说明部分,方便应用。

我们描述的消息采用如下格式,消息名[,参数1][,参数2]…

接收的消息、可以理解成他可以接受并执行的指令

1 串口命令模块 (com.lua)

向串口发送指定字符串,msg为消息字符串

COM,msg

2 OLED显示模块 (oled.lua)

DISPLAY,msg

向OLED屏幕输出字符串,msg为消息字符串,最长行宽为21英文字符,不支持中文,遇到中文字符按*显示代替,且占用两个字符位置

3 系统指令模块 (cmd.lua)

查询IMEI号,并从串口以如下格式返回数据: IMEI=XXXXXXXXXXXXX\r\n

GETIMEI

查询SN号,并从串口以如下格式返回数据: SN=XXXXXXXXXXXXXXXX\r\n

GETSN

4 UPWS服务器适配模块 (irtu目录)

透传上行(模块->服务器)发送数据data

NET_RAW_MONI,data

透传上行 (模块->服务器)发送json格式心跳包jsonstr

NET_HEART_MONI,jsonstr

透传上行 (模块->服务器)发送命令cmd

NET_CMD_MONI,cmd

5 UPWS服务器适配模块

6 GPIO双向控制模块 (bio.lua)

设置GPIO号为no的引脚为level电平(level=0 or 1)从串口返回如下格式的数据GPIOno=level

SETGPIO,no,level

获取GPIO号为no的引脚level电平 GPIOno=level

GETGPIO,no,level

上报的消息、可以理解成他对外界发出的指令消息

1 串口命令模块 (com.lua)

串口接收到的原始数据,数据为msg

COMRSV,msg

向服务器发送接收到的数据,str为数据,type为包类型

NET_RAW_MONI,str,type

显示消息,msg为消息

DISPLAY,msg

2 OLED显示模块 (oled.lua)

3 系统指令模块 (cmd.lua)

向串口回送命令执行结果,结果为msg

COM,msg

4 UPWS服务器适配模块 (irtu目录)

透传下行 (服务器->模块)收到数据字符串data

NET_RAW_MINO,data

透传下行 (服务器->模块)收到心跳包字符串heart

NET_HEART_MINO,heart

透传下行 (服务器->模块)收到命令字符串cmd

NET_CMD_MINO,cmd

显示消息,msg为消息

DISPLAY,msg

5 UPWS服务器适配模块

上报心跳包,字符串为heart

NET_HEART_MONI,heart

6 GPIO双向控制模块 (bio.lua)

向串口回送命令执行结果,结果为msg

COM,msg

模块的开放接口

对于其他的模块,我倾向于订阅、发布下面几个消息来完成,这个是重点

推荐使用的发布接口

显示消息,msg为消息

DISPLAY,msg

向串口回送命令执行结果,结果为msg

COM,msg

透传上行(模块->服务器)发送数据data

NET_RAW_MONI,data

透传上行 (模块->服务器)发送命令cmd

NET_CMD_MONI,cmd

推荐使用的订阅接口

串口接收到的原始数据,数据为msg

COMRSV,msg

透传下行 (服务器->模块)收到数据字符串data

NET_RAW_MINO,data

透传下行 (服务器->模块)收到命令字符串cmd

NET_CMD_MINO,cmd

通过以上的4+3=7个消息接口,我们希望能完成绝大多数的模块任务

今天就到这里,明天争取开始编码,晚安。

菜鸟学写Cat1 Demo集(二十五) 重撸架构 (2020-09-16)

八卦

人一旦真的着了急,就很容易拼命地努力,拼命努力的结果,就是动作变的生硬,而不到位,最终白白消耗了体力,进度不快反慢。 这就说我呢! 最近不知怎么的,突然就着急起来,整个demo集的开发,显得乱而没有章法,所以每一步走的都显得很蹒跚,这样下去项目一定会垮掉。 我时常告诫自己,当初了问题的时候,不是马上动手解决,而是先停下来,休息下,都想通了,再继续。 所以我今天决定停下来歇歇先。

完成功能回顾

经过了三周多的,努力,我们一共得到了如下几个模块

菜鸟学写Cat1 Demo集(连载)

1)串口命令模块 (com.lua)

通过串口发送demo测试命令,可以映射系统消息到1至多个串口

2) OLED显示模块 (oled.lua)

显示系统当前状态,比如信号质量,服务器连接情况等

将指令执行结果显示出来 (共两行,每行21个字符)

3)核心板适配模块 (bs.lua)

定义核心板硬件资源的分配方式

定义核心板识别标识

标签打印适配

4)iRTU网络通信包 (irtu_init.lua,irtu_over.lua,create.lua,default.lua)

采用稀饭放姜开发的iRTU组建,完成与服务器的数据链路功能

5)UPWS服务器适配模块

负责配合iRTU网络通信包按MTCP协议对数据进行编解码,联通远程控制端口和本地控制端口

6)GPIO双向控制模块 (bio.lua)

完成IO口高低电平的双向控制功能(可以输出上拉高电平或低电平,外部可以强制拉低引脚并被检测到)

模块修改

模块拆分

从以上模块分工来看, 标签打印适配和核心板适配这两个功能关联度并不高,因此应该进行拆分,新增一个系统指令的模块(command.lua)

模块调整

由于引入了iRTU模块,也遇到了一些新的问题。 网络心跳包是我新增的一个功能,用以上报模块的当前状态,这个心跳包的格式与通常的数据包不同,根据iRTU的架构,这种数据装包拆包要新建立一个通道,所以iRTU适配模块这部分功能代码要重新编写。

手机客户端登陆的时候,也会发来一些心跳数据,这个数据因为没有太多的价值,我们也不希望显示在屏幕上显示正文,而代之以一个小的标识图标。

模块增加

随着架构的逐渐膨胀,要用到的系统消息也越来越多,因此有必要进行相应的说明,这部分也将添加到bs.lua文件中

修改后的架构就酱紫了

菜鸟学写Cat1 Demo集(连载)

好了,今天就到这里了,我要去睡觉了,不好好睡觉,会变秀逗,这是真的。代码不急的…慢慢来

菜鸟学写Cat1 Demo集(二十四) 重新优化bs包 2020-09-15

八卦

最近我很郁闷,讲真。 什么东西都涨,就收入不涨。 原来涨价的大多是是一些高端的商品,或者非必需品。 今年以来逐渐波及到了生活必需品。 可口可乐作为 “神仙水”,这个名头可不是随便说说的,哪个熬夜的程序员敢说自己没有就着辣条一边晃脑袋憋代码,一边一罐一罐的猛喝”神仙水”。 可口可乐这东西,按成分上说,应该真的算是轻度兴奋剂了,有效成分是焦糖和咖啡因,前者可以为大脑提供能量,后者可以兴奋神经。 真是程序员居家必备良品。 更主要的是他平易近人的身价,过去打折促销这种易拉罐屏的1.8元就能买到,后来涨涨价也就到2.2元,闭闭眼也能忍了。 但这次他真的要对我下手了.. 左边的是原来的可乐,右边是新的立式包装,容量都一样是330mL,但价格从2.2变成了3.3..一口气涨了50%,原来的那种瓶子包装的可乐不卖了!

菜鸟学写Cat1 Demo集(连载)

我为啥想说这个事,其实我是想说说我对商品涨价的一个观察。 一般来说,一件商品从面世起,大家总是倾向于他要不断的降价,如果涨价,则往往会给这个商家贴上奸商,乱涨价的标签,然后用脚投票。 但是在商业的激烈竞争中,对于大多数快消品面世的价格本来就不高,其实根本就没多大利润空间,所以降价的同时往往伴随着”降质量”,这种策略在销量销量平稳的时候往往比较奏效。 但当出现特殊情况下,这个策略就很难执行了。 众所周知,快消品的生产成本和销量是息息相关的,比如可口可乐,由于基本是全自动化生产,销量只能影响到工厂的开工率,但是对于员工工资、场地这些刚性成本基本上没啥影响。 也就是说,理论上卖的少了,必须涨价才能维持原有的运转。 但销量锐减,你如果再涨价,肯定会加剧销量下降,形成恶性循环。 我讲这些,机智的大佬们肯定都当是小儿科,所以早就想到了应对的办法。 那就是旧产品停产,出产新产品,这样就有了重新定价的机会了。
而且,我感觉口味确实好像还好了那么一丢丢,可能是浓度高了,至少心理上是这感觉,但50%的涨价确实肉疼,只能自己偷偷掺水喝吧… 按100%勾兑白开水,一瓶变两瓶,1.65一瓶,这么看就不贵了,我是不是很鸡贼呀!

bs.lua 源码

今天基本上又要交白卷了,是的,因为代码仍然没有调通,我很希望我也可以像大神那样,代码一气呵成,但现实是,我并不能。 主要卡在了以下几点上:

1) 心跳包需要的核心板识别码,目前没有实现
2) 计划使用IMEI和SN用来产生识别码,但是这两个参数开机的时候并不能立即执行。所以没法用来初始iRTU的参数

所以今天只能继续优化核心板适配包bs.lua,目前考虑的策略是,使用nvm将核心板的信息预读出来,然后再处理,下面是还没调通的代码。 这与可乐掺水有没有关系,我不得而知,也许有吧…

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected],如有侵权嫌疑将立即纠正
---------------------------------------------------------------------------------------------------------------------------------------
--- 模块功能:核心板适配文件,适配硬件版本Cat1 Phone Core V2b
-- @author miuser
-- @module midemo.bs
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-05
--------------------------------------------------------------------------
-- @说明部分
--------------------------------------------------------------------------

-- 引脚       名称      功能
-- B1       GND         电源负极
-- B2       4V IN       锂电池供电输入
-- B3       5V IN       5V直流电源输入
-- B4       UART1_TX    串口1发送 
-- B5       UART1_RX    串口1接收 
-- B6       GPIO19      双向输入输出IO
-- B7       GPIO18      双向输入输出IO
-- B8       RESET       复位
-- B9       POWER ON    电源按键
-- B10      UART2_TX    串口2发送
-- B11      UART2_RX    串口2接收
-- B12      GPIO13      不建议使用
-- B13      1.8V OUT    1.8V供电输出
-- B14      MIC+        麦克风正极
-- B15      MIC-        麦克风负极
-- B16      SPK-        喇叭负极
-- B17      SPK+        喇叭正极
-- A1       GND         电源负极
-- A2       SCL         I2C总线时钟
-- A3       SDA         I2C总线数据
-- A4       SPI_CS      SPI总线片选
-- A5       SPI_CLK     SPI总线时钟
-- A6       SPI_MOSI    SPI总线数据输出
-- A7       SPI_MISO    SPI总线数据输入
-- A8       GND         显示屏供电负极
-- A9       VLCD        显示屏供电正极
-- A10      LCD_CK      显示屏时钟
-- A11      LCD_DA      显示屏数据
-- A12      LCD_RST     显示屏复位
-- A13      LCD_DC      显示屏命令数据切换
-- A14      LCD_CS      显示屏片选
-- A15      ADC3        模拟输入3
-- A16      ADC2        模拟输入2
-- A17      GND         电源负极
-- C1       GND         电源负极
-- C2       D-          USB差分数据负极
-- C3       D+          USB差分数据正极
-- C4       UART3_TX    串口3发送
-- C5       UART3_RX    串口3接收
-- C6       5V IN       5V直流电源输入

--------------------------------------------------------------------------
-- @用户可修改部分
--------------------------------------------------------------------------
----------------------引脚分配表-----------------------------
------------------------------------------------------------
------------------------------------------------------------
require "lbsLoc"
require "misc"
require "nvm"
module(...,package.seeall)
-- 将串口1,2,3及USB虚拟串口分配作为命令控制接口
COM_UART_IDs={1,2,3,129}  
--GPIO13,18,19映射为双向IO端口
BIOPins={13,18,19}

------------------------------------------------------------


------------------------------------------------------------
----------------------引脚映射表-----------------------------
------------------------------------------------------------
MAP={}
--核心板的B6引脚映射为GPIO19
MAP["B6"]="19"
--核心板的B7引脚映射为GPIO18
MAP["B7"]="18"
--核心板的B12引脚映射为GPIO13,这个引脚为内置LED使用
MAP["B12"]="13"
------------------------------------------------------------

--------------------核心板状态-------------------------------
--isLocated:number类型,0表示成功,1表示网络环境尚未就绪,2表示连接服务器失败,3表示发送数据失败,4表示接收服务器应答超时,5表示服务器返回查询失败;为0时,后面的3个参数才有意义
--LATITUDE:string类型,纬度,整数部分3位,小数部分7位,例如031.2425864
--LONGITUDE:string类型,经度,整数部分3位,小数部分7位,例如121.4736522
LONGITUDE,LATITUDE,isLocated=0,0,0
--本模块的IMEI号
IMEI=""
--本模块的SN
SN=""


--MTJ模块的ID
ID=""
--MTJ模块的MM
MM=""
--------------------------------------------------------------



---------以下内容为内部函数,不需要用户修改--------------------

--查找输入的参数是否存在映射替换
function PIN_MAP(boardpin)
   if (MAP[boardpin]~=nil) then 
        return MAP[boardpin]
   else
        return boardpin
   end
end

local function reqLbsLoc()   
     lbsLoc.request(getLocCb)
 end
--获取基站对应的经纬度后的回调函数
function getLocCb(result,lat,lng)
     log.info("testLbsLoc.getLocCb",result,lat,lng)
     isLocated=result
     LATITUDE=lat
     LONGITUDE=lng

     --获取经纬度成功
     if result==0 then
     --失败
     else
     end
     sys.timerStart(reqLbsLoc,20000)
 end
 reqLbsLoc()


  -- 加载关机存储模块 (关机需要保存的变量定义在该模块内)
require "NVMPara"
nvm.init("NVMPara.lua")
IMEI=nvm.get("IMEI")
SN=nvm.get("SN")
ID=nvm.get("ID")
MM=nvm.get("MM")
NVMPara.ServerTable=nvm.get("ServerTable")


if (IMEI=="" or SN=="" or ID=="" or MM=="") then 
    sys.timerStart(function() 
        --获取IMEI和SN号
        IMEI=tostring(misc.getImei())
        SN=tostring(misc.getSn())  
        ID=string.sub(IMEI,-10)
        MM=SN
        --保存参数
        nvm.set("ServerTable",{ip="box.miuser.net",port=7101,})
        nvm.set("IMEI",IMEI)
        nvm.set("SN",SN)
        nvm.set("ID",ID)
        nvm.set("MM",MM)

        log.info("boardinfo","restarting to save para")
        rtos.restart()
    end,5000)
end

sys.timerLoopStart(function() 
    log.info("boardinfo","IMEI=",IMEI)
    log.info("boardinfo","SN=",SN)
    log.info("boardinfo","ID=",ID)
    log.info("boardinfo","MM=",MM)
end ,3000 )

菜鸟学写Cat1 Demo集(二十三) JSON心跳包 2020-09-14

八卦

道理比谁都懂,讲就天下无敌,做就无能为力。 对于大多数人而言,的确是这样的,比如我。 这观点虽然看起来有点楞,但是如果你细分析一下,是合理的。 因为”讲”本身是一个技能需要练,”做”是另一个技能,也需要练。 如果两方面都要练好,则必定要加倍努力才行。 而人的精力往往是有限的,所以大多数时候,那些天天把事”做”的很精的人,”讲”的技能却未必很行。 这并不矛盾。 我属于能说不能干那种,说起来条条是道,真做起来就捉襟见肘。 比如今天的这篇文章,截止到现在,代码也没有最终调通… 我也是很捉急。 不过很多事急不得,今天写不完,就明天写。明天写不完,就后天写。 今日复明日明日何其多,没事儿,不急。 如果迷迷糊糊代码没写好,返工,反而慢,这种费力不讨好的事,我一般尽量少做。

秦总的物联网小说,今天有更新了《第七章2019年的市场形势》。 全篇文章看点不断,最惊讶的是文末的结论:

如果我们的份额上不去,一定是合宙自身服务能力出了问题,而不是竞争对手出了什么牌。

确实写得很有勇气,讲真,不是每个人都有勇气承认自己的不足,更何况是众目睽睽之下承认,这点确实非常钦佩。 如果您也有兴趣一读,敬请关注公众号《合宙Luat》,更有五折购物券每文必送。

代码重构

由于要实现与H5小程序的配合,因此今天的编码主要集中在upws.lua上,对其他的几个文件也有相应的更新。 主要完成几个事

1) 定义了ID,MM这两个参数用以对核心板身份进行定义

2) 定义了系统状态的json心跳包,便于demo集脚本上报核心板状态

3) 修改irtu.json,取消iRTU固件原有心跳包。

4) 在uspws.lua里新增心跳包逻辑

编码

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected],如有侵权嫌疑将立即纠正
---------------------------------------------------------------------------------------------------------------------------------------
--- 模块功能:upws服务器适配
-- @author miuser
-- @module midemo.upws
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-014
--------------------------------------------------------------------------
-- @说明部分
--------------------------------------------------------------------------
require "net"
require "misc"
require "common"
module(...,package.seeall)

function gb(str)
    return common.utf8ToGb2312(str)
end

--回报状态信息
function ReportStatus()
    rssi=net.getRssi()
    tm = misc.getClock()
    ret=
    {
        --核心板IMEI
        ID=bs.IMEI,
        --核心板SN
        SN=bs.SN,
        --核心板ID
        ID=bs.ID,
        --核心板MM
        MM=bs.MM,
        --网络状态
        NETSTATUS=create.getDatalink(),
        --基站定位成功
        isLocated=bs.isLocated,   
        --经度
        LONGITUDE=bs.LONGITUDE,
        --维度
        LATITUDE=bs.LATITUDE,      
        --设备端时间戳
        TIME=tm,
        --信号强度
        RSSI=rssi,    
    }
    dat=json.encode(ret)
    sys.publish("NET_RAW_MONI",dat)
end

--服务器连接成功后定时发送心跳数据包
sys.taskInit(function()
    while (NETSTATUS==false) do 
        sys.wait(1000)
    end
    while true do      
        ReportStatus()  
        --每10S发送一次心跳
        sys.wait(10000)
    end
end)

详细的内容明天再继续讲,我这人最大的毛病就是发挥很不稳定,没办法,素质就这意思,您多多包涵可以么?

菜鸟学写Cat1 Demo集(二十二) 手机控制原理 2020--9-13

八卦

众所周知,现代商业最重要的是生态,生态是啥,生态其实就是有人愿意跟你玩。 你做的东西是不是最优的不重要,有人愿意跟你一起做才重要。 不然,没人跟随,顶级的技术也可以荒芜变成铁锈。 有人跟随,一般水平的技术可以不断精进成为真金。 那么怎么吸引别人跟进就成为一个最核心的技术。 这技术我也在合宙大学学习,只能把我学到的讲给您听,我学的是不是真本事,理解的有没有偏差,这都难说,您就抱着看看玩玩的心态就行了。别跟我较真。

开源是一种商业手段,而非目的。 这是我在合宙工作时一个巨佬在讲授商业原理时提到的,我一直铭记在心。 为啥说这个呢,因为最近网上炒得最热的话题可能就是华为宣布鸿蒙操作系统开源。 讲真,开源社区,特别是中国的开源社区我是纯新手,纯菜鸟。我如果乱讲一番,那台下就指不定飞什么上来了。 所以我不讲国内开源,我说说我知道的关于通讯协议标准的事儿,这个是我亲历的。 但是我表达能力又不行,直接讲我也怕讲不起来,所以我就邯郸学步,模仿下秦总。 讲讲故事,说说这个事儿。

这故事讲,某人想造一艘大轮船,就邮轮,载客的那种。 但是他只有有限的一点铜板,所以要造这艘大船,他就必须先筹集足够的资金,最好的募集对象可能就是乘客。 相当于预售船上的座位。 当然,一次的船票钱可能不够,那就卖月票,这道理我不啰嗦,类似于众筹,您都懂。 那么好了,假定这个人德高望重筹到了这么一笔钱,可以开始造船,问题又来了,靠什么盈利。 必须得有外部的乘客来,才能盈利。 因为如果你众筹的时候就把自己的利润算进去,这众筹你是筹不起来的。 谁也不可能为了一张存在兑付风险的船票付出高于市场价的成本,所以为了兑价这个风险,船票必须打折。 而发起人为了表示诚意,往往也要出相当比例的自有资金,以显示诚意。 所以船造出来了,这一切才开始。

好,问题又来了。 你一搜新船,下海安全不安全人家都不知道,人家未必敢坐。 虽然如果你是马云,或是马化腾就没什问题,但出身也很重要呀,反正我不认识这二位。 那么好,最好的办法吸引人来坐船当然就是免费! 免费这两个字是不是靠上一点了?

那好,下一步,如果船票都免费了,靠啥挣钱。 首先说,船里的位子一定有各个仓位,头等舱,二等舱,三等舱,更差的还有舱底,一般只能装货。 付钱多的,肯定头等,没跑,那免费蹭船的就有啥算啥了。 对吧,这很公平吧,不能吃免费的馍,还嫌面黑。 当出钱买票的客人越来越多,不再需要免费的客人充数的时候,项目的开源周期也就结束了。 这也是很多git上开源的项目跑路的原因。 因为航线已经建立,不需要搭船客了。

当然也有一些比较厚道的公司,把开源一直坚持下来,但是开源也一定是有前提的,那就是商业客户优先。 保留一定的席位给开源的客人,这种公司应该说已经是相当的厚道了,不是么,难道要求商业公司一定要把股东的钱败光么?这不科学,而且也注定不长久。 这种开源极端分子永远只能是昙花一现,因为不符合客观规律,不尊重客观事实。 客观事实就是人总要吃饭。

听我讲了这么多,您大概也就理解了鸿蒙开源的原因了吧。 我们假定华为足够厚道,把开源作为长期战略坚持下去,那么如果您是华为的老大,把为数不多的免费席位留给什么样的人呢? 是给那些薅羊毛为己任的,上船就抢好座位的人呢,还是那些在船上一路站着免费为其他付费客人默默提供服务的人呢?

手机端控制原理

在写代码之前,我先撸一撸我前面讲的iRTU+Upws+Websocket控制端构成的全栈demo的套路给大家说一下,先上图:

菜鸟学写Cat1 Demo集(连载)

这图足够简单吧,应该都能够看得懂。

我们做的事儿很简单,就是在手机浏览器和Cat1 Phone Board 之间架设一个命令管道,可以完成双向的数据传输。 具体说传输的数据就是符合MTCP协议的一组字符串,如果您有兴趣可以读一读这个协议,也不难

http://box.miuser.net/MTJMCN/MTCP.html

约定了通讯的数据格式后,我们还要定义一组消息,用以传输数据。 我举个虚拟的例子

手机发送一个ECHO的消息, Cat1 Phone board 回复一个ECHO 消息,就是最简单的回声测试,用以检测硬件设备是否存在。 这就是最简单的了。

虚拟的例子不好,容易让人误解,下面我们直接上真实的例子。

我们希望手机客户端可以知道几个事儿

1)获取当前系统状态

因为通常情况下,我们都需要一个心跳包来维持连接,而且国内4G网络的最小数据计费单位为1K,也就说,你包小了,也不省啥流量,所以我们把心跳包和系统状态包二合为一,减小了数据发送频次,从而降低了网费消耗。

对于心跳包,客户端不需要发送指令,这也是MTCP协议规定的唯一的一个只要设备在线,就会定时自动发送的数据包。其余的数据都必须根据客户端发送相应的请求来实现。

心跳包的格式,我们希望是一个JSON字符串,里面暂时包含如下信息:信号强度、联网方式、系统时间。

{CSQ:”XX”,BoardTime:”TIME”,CONNECT:”METHOD”}

2)设置GPIO状态

发送:

SETGPIO,[GPIO号],[1或0]

返回:

SETGPIO,[GPIO号],[1或0]->OK

3) 读取GPIO状态

发送:

GETGPIO,[GPIO号]

返回:

GETGPIO,[GPIO号]->OK
GPIOSTAT,[GPIO号],[0,1]

我们要按照这两个格式分别对lua代码和html代码分别编程,明天我们正式开始,今天先到这里,谢谢您的阅读。 请您早点休息,以便积极迎接新一周的紧张工作。

菜鸟学写Cat1 Demo集(二十一) 手机控制小试 2020-09-12

八卦

今天是周末,我给大家讲一个小故事。 这个故事是我的老恩师当年讲给我听的,我一直奉若珍宝,今日奉献给大家。 我的老恩师是60后,中国三反五反后第一批大学生。 那个时候大学还都是包分配的,我老师就被分配到了天津计算机研究所。 从名字我们就可以知道这显然是一个国企。 80年代,计算机绝对是新新事物,带来的冲击力堪比今天的人工智能。 甚至更胜一些。 那时候见过电脑的人没几个,即使是从电视上也是如此。 更别说去碰计算机。 但是我的师傅,他的单位得到了一个机会订购一台IBM微型机,资金来源属于中央拨款。你可以想象这在当时是个什么场面,当时是从IBM总部要来了Quotation, 然后组织全组领导开会决定选择计算机的相关配置。 资料当然也是全英文的。 报价单上主要内容由两部分构成: 第一部分是部件的名称,第二个部件是价格。

毕竟,计算机所里的各位老师也都不是菜鸟,起码比一般老百姓懂得多得多,再加上查阅国外文献,对于要买的东西了解的其实也是八九不离十。 Monitor,Workstation,Harddriver,Memory…这些我不说了,您也都知道是啥。 但是报价里有一项内容大家都不太懂叫:Operation plane 这个东西大家都拿不准。 你要知道当时能决定最终购买配件的人一定又是专家中的专家,研究所的领导。 所以谁也不敢轻易说自己不懂, 于是大家发表意见。 一位德高望重的专家就说:这个平台支持系统运行,应该是关键部件要买。 其余专家也就都附和说,嗯操作平台必不可少,没有平台,计算机无法运行,还有的添油加醋说,平台是计算机运行最核心的部件。 这个所谓平台,报价是8000美金,大家可以回想下,在80年代8000美金是啥概念。 最终订购的电脑运回来了, Operation plane 是一个不锈钢台面的桌子,据说质量不错… 对,您没理解错,就是一个桌子,负责支撑电脑的平台,8000美金。

手机控制端

在第十九章,我们尝试用iRTU固件建立起了和UPWS服务器的UDP连接,UPWS的全称是UDP plug websocket, 顾名思义,他一定是有websocket接口的, 这个接口就是专门为和手机配合用的。 一般来说手机上的用户界面有几种形式

1) 独立APP,就是应用商店转个程序,这个大家都懂,但是我相信没几个愿意轻易给自己电脑装那么多APP当垃圾

2) 基于APP大家比较反感,微信推出了小程序,这个就比较好了,通常是即扫即用,不用既抛。 随说其实也不是真的抛掉,应为微信小程序都有本地缓存,但至少心理上好接受一点,因为看不到独立的图标。 在实际上微信小程序占用的资源也比APP小。

3) H5页面,对于Html5标准而言,一个网页可以做的和APP非常的逼真,各种UI元素一点都不少,如果缓存处理得当,加载速度甚至比小程序都快, 占用资源比小程序更小。 而且还有一个最大的好处,Html5页面同时兼容各种手机平台甚至包括PC的浏览器。 是事实上兼容性最好的控制环境。

综上所述,我选择的远程控制终端就是H5页面,我把他架在了box.miuer.net上, 测试页面完整的地址是:

http://box.miuser.net/H5_Web_demo/switch.htm?ID=0000000001&MM=0000000000000001

源码我就不提供了,因为不必要,html5都是可以直接把页面保存下来的,页面我也没加密,所以您所见即所得即可。

代码其实我是用过去控制202开关的代码改过来的,而且还没改完。
菜鸟学写Cat1 Demo集(连载)
您先看个意思。 明天我再详细讲。 实现的内容是通过开关,控制GPIO13小灯的打开和关闭。 后面上代码,您先试试读读吧,明天我详细讲。 周末了,大家少看电脑,多休息:

<!DOCTYPE html>
<html lang="en" >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=480" />
    <title>Websocket开关</title>
    <link rel="stylesheet" href="css/normalize.min.css">
    <link rel="stylesheet" href="css/style.css">
    <SCRIPT src="./main/jquery-1.8.3.min.js"></SCRIPT>
    <script src="./main/reconnecting-websocket.min.js" ></script>
    <SCRIPT src="main/JustGage.js"></SCRIPT>
</head>
<body>
<h1>Websocket 开关控制器<a href="www.miuser.net" target="blank" rel="noopener"></a></h1>
<!-- Inspiration – https://dribbble.com/shots/525358-Buttons-Lights-Shadows?list=searches&tag=toggle_button&offset=10 -->
        <h2 class="headingOuter"><div align="center" id="online" >点击控制电源插座</div></h2>
        <div class="toggle toggle--knob">
            <input type="checkbox" checked onclick="setSwitch()" id="toggle--knob" class="toggle--checkbox">
            <label class="toggle--btn" for="toggle--knob"><span class="toggle--feature" data-label-on="on"  data-label-off="off"></span></label>
        </div>
        <h2><div align="center" id="device_online" >设备状态未知</div></h2>
        <h2><div align="center" id="ID" >ID:0000000000 </div><div align="center" id="msg" >created by miuser</div></h2>

</body>
<script>
    var socket;
    var TTL;
    var remoteSWStatus
    // window.onerror=function (message) {
    //     try{
    //         console.log(message);
    //     }
    //     catch(ex){}
    // }
    var repeatLoopUpdateSwitch
    window.onload = function(){
        try{
            connect();
            var ID=getUrlParam("ID");
            ("#ID").html("ID:"+ID);
            document.getElementsByTagName("title")[0].innerText = "开关 ID:"+ID;
            repeatLoopUpdateSwitch=setInterval("LoopUpdateSwitch()",3000)
        }
        catch(ex){}
    }
    window.onbeforeunload = function() {
        try {
            socket.close();
            socket = null;
        } catch(ex) {}
    };
    //从URL参数栏取得参数
    function getUrlParam(name)
    {
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|)"); //构造一个含有目标参数的正则表达式对象
        var r = window.location.search.substr(1).match(reg);  //匹配目标参数
        if (r!=null) return decodeURI(r[2]); return null; //返回参数值
    }

    //连接websocket
    function connect() {
        var host = "ws://box.miuser.net:9000/";
        socket = new ReconnectingWebSocket(host);
        try {
            socket.onopen = function(msg) {
                //alert("连接成功!");
                ("#msg").html("服务器连接成功");
                HeartBeat();
            };
            socket.onmessage = function(rsv) {
                var respond=rsv.data.toString();
                var source=respond.substring(7,9);
                var len=parseInt(respond.substring(2,4));
                var content=respond.substring(39,len-2);
                if (source==="01")
                {
                    if(content==="GPIO13当前电平为高")
                {

                    remoteSWStatus="ON"
                }
                    if(content==="GPIO13当前电平为低")
                    {
                        remoteSWStatus="OFF"
                    }
                    TTL=600;
                    sendstatus="Done";
                }
            };

            socket.onclose = function(msg) {
                //alert("socket closed!")("#msg").html("伺服器连接异常");
            };
        } catch(ex) {
            log(ex);
        }
    }
    function HeartBeat() {
        var ID=getUrlParam("ID");
        var MM=getUrlParam("MM");
        var msg = "005132A08"+ID+MM+"1234Air724STAT05";

        socket.send(msg);

    }
    setInterval("HeartBeat()",300000);

    function OnlineDetect() {
        if (TTL>0)
        {
            ("#device_online").html("设备正常");
        }else
        {("#device_online").html("设备离线");
        }
        TTL=TTL-1;

    }
    setInterval("OnlineDetect()",1000);

    function SendCMD(cardcmd)
    {
        var ID=getUrlParam("ID");
        var MM=getUrlParam("MM");
        if(cardcmd=="on") socket.send("005332A08"+ID+MM+"1234SETGPIO,13,105");
        if(cardcmd=="off") socket.send("005332A08"+ID+MM+"1234SETGPIO,13,005");
        socket.send("005032A08"+ID+MM+"1234GETGPIO,1305");
    }

    var repeatLoopExeCMD

    function setSwitch()
    {
            sendstatus="";

            //先执行一次,再每3s执行一次
            LoopExeCMD();
            repeatLoopExeCMD=setInterval("LoopExeCMD()",3000)

    }

    //发送指令,直到状态被同步
    function LoopExeCMD()
    {
            //设备不在线则不发送

            if (TTL<0) return;
            //sw是反向的,true 是 关闭,false 是打开
            var sw=document.getElementById("toggle--knob")
            if ((sw.checked === true) &&(remoteSWStatus!="OFF"))
            {
                SendCMD("off");
                ("#online").html("正在更新开关状态...");
                remoteSWStatus="UNKNOWN";
            }
            else if ((sw.checked === false) &&(remoteSWStatus!="ON"))
            {
                SendCMD("on");("#online").html("正在更新开关状态...");
                remoteSWStatus="UNKNOWN";
            }else
            {
                if (remoteSWStatus=="ON")
                {
                    ("#online").html("开关状态:打开");

                }
                if (remoteSWStatus=="OFF")
                {("#online").html("开关状态:关闭");
                }
                clearInterval(repeatLoopExeCMD);
            }
    }
    //更新程序打开时的开关状态
    function LoopUpdateSwitch()
    {
        //设备不在线则不发送
        if (TTL<0) return;
        //sw是反向的,true 是 关闭,false 是打开
        var sw=document.getElementById("toggle--knob")
        if ((remoteSWStatus!="ON")&&(remoteSWStatus!="OFF"))
        {
            HeartBeat();
            ("#online").html("正在更新开关状态...");
            remoteSWStatus="UNKNOWN";
        }
        else
        {
            if (remoteSWStatus=="ON")
            {("#online").html("开关状态:打开");
                ("#toggle--knob").attr("checked",false);
            }
            if (remoteSWStatus=="OFF")
            {("#online").html("开关状态:关闭");
                $("#toggle--knob").attr("checked",true);
            }
            clearInterval(repeatLoopUpdateSwitch);
        }
    }


</script>
</html>

页面的效果是酱紫

菜鸟学写Cat1 Demo集(连载)

点击开关可以使开关打开或者关闭,好了今天天就到这里,点灯大湿期望明天与您再相会在doc.openluat.com
菜鸟学写Cat1 Demo集(二十) 番外篇-iRTU工作原理学习 2020-09-11

八卦

我最近两年基本上都在各种社群里混,最近临近2020年的第四季度,感触良多。 讲真,做物联网这个行业的大佬平均年龄大多数是在30-45岁这个区间居多,将创业这种想法落实到行为上的一般都是纯爷们(我除外)。 但即使是这样,现在这个环境也让这些大佬头顶上的鸭梨儿相当的大。 达到什么程度呢:夜不能寐,茶饭不思绝不是夸张的说法。 人都是有情绪的,无例外。 当出现正向反馈时,自信上升->效率提高->工作成绩提高->自信继续上升,螺旋上升。 但一旦反过来出现反向正反馈,自信心下降->效率下降->工作成绩下降->自信心进一步下降,那真是有一种让人回天无力的感觉。 你问我是不是这种感觉,我肯定不是,因为我长期在底部徘徊,没怎么太上升过,哈哈。

但是昨天的确发生了一个事儿,我的心情为之振奋了一小下。 据称,Cat1 phone board这个项目有希望被纳入合宙生态项目,我甚至还在合宙市场部大佬的帮助下建了个技术支持群。 这样子这个核心板总算是像个项目的样子了,看到自己两年的努力总算是有了那么一点点的反应,内心还是些许激动地。 希望后面可以越来越好吧。

iRTU网络通信原理

我不懂iRTU,一知半解都算不上。 但是昨天群友就跟我要求要我讲解iRTU的原理,说我昨天讲的内容听不太懂。 所以今天只好把原来的计划放一放,硬着头皮讲一讲iRTU,毕竟人家是付了费的用户,给我一块钱也是我的财神爷。财神爷的事我可不敢怠慢。 所以今天我就试着跟大家一起分析下iRTU。

iRTU是一个很庞大的项目组,虽然只有5个文件,但代码量并不少。 如果完全讲下来,估计一个星期都讲不完。 所以考虑再三,我今天只大概讲解下 我们用的部分:UDP透传, 因为目前只有这个部分代码和我们目前的项目相关。 如果以后涉及其他部分的代码,我们遇到了再讲。

先说说iRTU的设计思路,当然这些都是我猜的,如果您想知道准确的,直接去找稀饭老师问好了。

iRTU固件把网络看成一个管道,对于我们当前的场景,一端是UDP的Socket,另一端则是一个串口。 这是iRTU的基本原理,就这么简单,完了。

进一步iRTU把网络socket看成了若干个管道,准确的说是八个。 您可以把这些管道映射到一个或多个串口上,这样就实现了多网络接口同时上网。 具体的通讯机制,如下:

1) 为每一个网络端口,设置一个独立的Task消息名称,通常后缀是_1到_8 ,比如 NET_RECV_WAIT_1,就是网络收到了数据以后会触发的消息。

2) 收到消息后,对应的映射到的串口把收到的数据发送出去。

3) 数据流模板是网络端口和硬件串口之间的一个中间件,负责数据格式的转化。

如果是发送数据,这个流程正好反过来,我就不赘述了。

iRTU与demo集通讯

前面讲过了网络通信原理,大家可以看出,在iRTU固件中所有的透传数据是通过消息机制完成传输的,这样我们也就有了进行干预的机会。 具体说就是关闭串口的物理端口,我们直接去与iRTU网络部的消息处理函数进行通讯,这样iRTU就可以与现有的代码通信了,是不是很简单。

这部分代码建议您认真读一下 irtu_init.lua,irtu_over.lua 就懂了, 如果不懂,就再读一遍。 当然,如果您买了我的开发板,也可以来群里提问,我现场解答。

必须承认,开发板198的价格确实有点高,但我会尽力而为把我为数不多的本事多教给您作为补偿。

菜鸟学写Cat1 Demo集(十九) 连接云服务器 (2020-09-10)

八卦

我不懂金融。 真的,一点都不懂! 最近这几年买的金融产品基本上都是陪本的,而且赔的很多。 金融市场是一个最纯粹的市场经济,公买公卖。 既不会有人逼着你参与,也不会有人逼你退出。 他就在那,随你出入,挣钱是本事,赔钱也怪不得谁。 我举一个例子,头几年,创业板刚开始的时候,我买过华锐风电,因为那时候原油价格140一桶,我觉得新能源一定有前途,又是国家号召的,还是上证所开山祖师魏文渊牵头的,真可谓天时地利人和,肯定没问题。 但最终退市,所有投入血本无归。

最近金融市场火爆,科技股几乎都是今天买明天赚。 在这里我奉劝各位年轻人,入场要冷静,特别记得愿赌服输。 我这有前车之鉴,千万不要学我。 今天你以为不费吹灰之力挣了辆车,明天就可能搭进去一套房。 Easy money劝大家尽量不要碰。 打工固然辛苦,但是每一分钱都是辛辛苦苦挣来的,自然格外珍惜,所谓落袋为安,永远是自己的。 这样的日子过得踏实,你身边的人也踏实,一步一脚印的走,相信苦尽一定甘来。

久赌无胜家,勤勉打天下,这句话我与诸君共勉吧。

云服务

要说云服务,当下可是林亮满目,不仅功能繁多,各种存储架构,各种协议,各种支持,什么AI,免编程,图形化,都是小儿科了,关键不少平台还不收费,这真是奇葩。 就好像你去市场买东西,看到店家不仅不要你钱,还请你免费吃。 当然,天下没有免费的午餐,所谓的免费,这是由于现在激烈的市场竞争造就的。 云平台技术层肯定各家都有自己的粘性,你一旦用了它家的技术,在这个底层框架上开发出来,那么说将来想改旗易帜,绝非易事,各种坑等着你。 这个套路最早都是跟马爸爸学的,应该说,他带了一个坏头。 先免费,后高价,专门杀熟成了互联网行业的通则,没有人再可以轻易改变了。

我这个人,最不喜欢的就是在别人的平台上搞开发了,所以,别人的云平台再好,我也不愿意用。 其实云平台也有很多开源的,这种的我也不排斥,因为以后他乱涨价,我可以不用。比如 EMQX就不错,反正MQTT协议也是通用的,这挺好的。 但是我还是不满足,因为现有的协议虽然性能很优秀,但其实也相当的复杂,没有几个人有能力自己去读懂MQTT服务器的所有代码。好吧,我这话说的不对,也许您可以。但至少我不能。 我需要的是一款特别简单,好懂,代码量超级简单的服务器。我也没啥要求,就是想通过手机或者电脑能遥控物联网模块就行了,多一点功能我都不需要。

我寻边全网,发现现有的各种服务框架都太复杂了,应该说都是商用或者准商用级别的,都超出我的学习能力太多了,所以最终,我决定动手自己开发,也就有了这个开源项目,uPWS,这个项目前前后后也运行了两年了,目前看做到7X24小时不挂是没问题的,不信您可以用高防服务器打一打试试,就算帮我做压力测试了,我非常感谢。 但如果您发现问题,请联系我(QQ:64034373)。

服务器的地址是

udp://box.miuser.net:7101

ws://box.miuser.net:9000

wss://box.miuser.net:9002

这三个端口作用是基本相同的,完成对MTCP协议的数据包进行存储转发,目前稳定最大吞吐率是2500 package/S, 相比商业系统的几十万吞吐量小了两个数量级,因此无法完成大量数据的传输,但是传个温湿度,开关信号啥的绰绰有余。 关键是协议非常简单,您自己一看就懂。 服务器也简单,就是一个exe文件,随便找一台windows就可以运行,普通家用机都可以,只要有外网端口就行,据我了解很多地方的联通光纤都支持。 但是很可惜我所在的城市,自从今年换了一批华为的新网关设备,就不支持了,真的很郁闷。 不然可以省下一大笔云服器费用。现在我在用阿里云,真的贵的要死。

项目的开源地址是:

https://gitee.com/miuser00/upws

您能看一眼是我莫大的荣幸

连接云服务器

昨天我们通过努力将iRTU集成到了DEMO集中,当然也就要用iRTU的网络功能进行数传。 具体的方法是对irtu.json的相关字段进行配置,具体内容如下:

{
    "fota": 0,
    "uartReadTime": "25",
    "flow": "",
    "param_ver": "1",
    "pwrmod": "normal",
    "password": "",
    "netReadTime": "0",
    "passon": 1,
    "nolog": "0",
    "plate": 0,
    "reg": 0,
    "convert": 0,
    "uconf": [
        [],
        []
    ],
    "conf": [
        [
            "udp",
            "005501B01000000000100000000000000011234iRTUUPWs 1.0.000",
            10,
            "box.miuser.net",
            7101,
            1,
            "",
            "",
            ""
        ],
        [],
        [],
        [],
        [],
        [],
        []
    ],
    "preset": {
        "number": "",
        "delay": "",
        "smsword": ""
    },
    "apn": [
        "",
        "",
        ""
    ],
    "cmds": [
        [],
        []
    ],
    "pins":[
        "pio4","",""
        ],
    "gps": {
        "pio": [],
        "fun": []
    },
    "upprot": [
        "\nfunction\nlocal str=...\nlocal totallen=41+str:len()\nlocal ret=string.format(\"%04d\",totallen)..\"01\"..\"C\"..\"01\"..\"0000000001\"..\"0000000000000001\"..\"1234\"..str..\"05\"\nreturn ret\nend",
        "",
        "",
        "",
        "",
        "",
        ""
    ],
    "dwprot": [
        "function\nlocal str=...\nlocal ret=str:sub(40,-3)\nreturn ret\nend",
        "",
        "",
        "",
        "",
        "",
        ""
    ],
    "warn": {
        "adc0": [],
        "adc1": [],
        "vbatt": [],
        "gpio": []
    }
}

稀饭老师写了非常详尽的格式说明文档,您可以看一下,我也已经上传到了服务器

http://file.miuser.net/pool/JSON%20%E6%A0%BC%E5%BC%8F%E6%95%B0%E6%8D%AE%E5%8D%8F%E8%AE%AE%E8%A7%A3%E9%87%8A.pdf

概括起来其实很简单: 按照指定的数据格式连接服务器UDP端口,数据格式是通过上下行数据流模板来实现,并且按时发送心跳包维持连接。 文件配置好,然后和源码一起烧写进去就可以了。 然后我们通过PC访问UDP端口或者手机访问webscoket端口都可以实现与midemo的通讯。 我测试的时候用的是PC,这是源码的文件包: 也欢迎您下载试试,代码环境是VS2019,使用C#语言编写。 下载地址是:

http://file.miuser.net/pool/CS_UDP_demo.zip

菜鸟学写Cat1 Demo集(连载)

程序运行后,点击左侧的连接服务器即可

菜鸟学写Cat1 Demo集(连载)
UDP相对于TCP传输的优势和劣势都比较明显。 我简单说说: TCP/IP带有自纠错功能,因此相对可靠性更高, UDP虽然也带包完整性校验,但并没有错误重发机制,因此发送的数据可能在传输过程中丢失,虽然在4G或者宽带网络中这种情况并不常见。 但是他带来的是更低的网络传输延迟和更小的数据流量开销,这两点其实也都很珍贵。 而且,无论是使用TCP/IP还是UDP/IP协议,我们在生产环境中往往也要在应用层进行相应的差错重传处理,否则是无法交付的,在底层采用UDP因而会带来的是更高的效率。因此在简单数据传输场景,采用UDP协议的UPWS开源服务器还是有很大的用武之地的。 另外这个服务端代码很短小,只有几千行代码。 对于想深入云服务核心技术的学生来说,相比动辄几十万行的开源框架,UPWS应该更容易读懂,可以方便您学习在今后的日子里纵深钻,当一块垫脚石。

今天的肩膀很疼,就写这么多了。明天我们再介绍手机控制的方式。 再次感谢您的耐心,一直看到这里,您的阅读是我写下去的动力,谢谢。

菜鸟学写Cat1 Demo集(十八) 挂载iRTU核心 2020-09-09

八卦

稀饭老师是我在合宙社区认识的第二位老师,第一位您可以猜一猜是谁。 这两位老师有一个共同点,就是全心全意的和工程师交朋友,不说客气话,不玩虚的,简明直接的把自己的看家的本领无私的教给社群的小伙伴们。 当然这和合宙所推崇的企业精神不无关系。

菜鸟学写Cat1 Demo集(连载)

讲真我就做不到,因为我没啥本事可教…很多人骂有的工程师保守,自己有本事不教别人。 这种人我是非常鄙视的,人家凭啥教你? 教会徒弟饿死师傅这句古话是有道理的。 你客客气气的,人家喜欢教你。你天天吹牛,那人家不搭理你理所应当。 我在社群里最看不惯有些颐指气使的客人,买了两个模块,就在那对群主和管理员呼来喝去,稍有怠慢话里话外各种长辈尊称就都上来了。 讲真,幸亏我不是管理员,不然这样的人,我见一个踢一个! 做买卖,我是卖艺不卖身的,人格不能丢,您说是不是这个理儿?

挂载iRTU

昨天因为身体的关系,我没来的及把iRTU的整合套路写出来。 就匆匆忙忙的把代码贴出来了,更要命的是贴的还不对,肯定是不及格的。 今天我连代码,带说明,一并补上,希望您能谅解我。

菜鸟学写Cat1 Demo集(连载)

iRTU裁剪

首先先说说iRTU的剥离过程。 众所周知iRTU固件是合宙开源社区的技术大拿,不仅开发了luatask,iRTU等一系列优秀的开源软件,还开发了core系列的核心板,最近又有新作X-magic box,真可谓是软硬通吃,是个不折不扣的全栈。 我们今天单讲iRTU。

iRTU固件是一个跨平台的免编程透传固件,用lua语言编写,兼容全系列2G 4G Cat1,Cat4 模块。 我们目前只用到Air724这个模块,因此对于iRTU固件,我进行了相应的裁剪。 下面是具体的步骤

1) 移除 1802,1802S,8955 三个框架软件包
2) 移除 main.lua 引导头

别听我说的天花乱坠的,其实炒鸡简单,就是把不用的文件七七八八的删掉就行了。 具体说就是保留以下几个文件:

1 保留 lib_RDA8910 目录所有文件,这是luatask3.0的底层框架,这个东东太复杂了,我不敢动。

2 保留一下iRTU几个核心文件

create.lua 负责连接各种网络的功能

default.lua 负责模块本地初始化和各种硬件资源的控制

tracker.lua GPS外挂模块

iic.lua 温湿度传感器外挂模块

iRTU封装

删除完文件,iRTU当然也就没法工作了,头文件都没有,运行都运行不起来,因此,我们要给iRTU提供一个引导的途径 具体是上说,就是编写两个适配文件 。这个原来计划是就只有一个,但是考虑到iRTU本身可能和原有的框架发生硬件资源争用,所以我又写了一个挂载完成后调用的适配文件,用于回收iRTU非必须使用的硬件资源

irtu_init.lua 在iRTU核心前加载,负责为iRTU配置合适的运行环境,具体上说完成以下几个事儿

1 从外部配置文件 irtu.json 加载默认的iRTU配置,当前的配置是与upws服务器进行关联

2 将mi_demo的消息处理函数和iRTU的消息处理函数进行连接,类似于硬件中的给模块接信号线。

3 提供iRTU运行的必要全局变量环境

irtu_over.lua 在iRTU核心加载后执行,负责释放掉iRTU不用的硬件资源,目前主要是对GPIO进行有序释放

封装后,iRTU与外部软硬件环境接口如下:

软件:

1 NET_RAW_MINO(Module Input,Net Out) 网络数据到达,这是我们从服务收到数据的接口
2 NET_RAW_MONI(Module Output,Net Input) 网络数据发送,这是我们发给服务器的数据接口
3 根据命令字符的第一部分进行系统消息分发

具体代码如下:

sys.subscribe("NET_RECV_WAIT_1",function(uid,msg)
    local str =msg
    -- 串口的数据读完后清空缓冲区
    local splitlist = {}
    string.gsub(str, '[^,]+', function(w) table.insert(splitlist, w) end)
    local count=table.getn(splitlist)
    --sys.publish("UARTIN",str)
    for i=1,#splitlist do 
        splitlist[i]=common.gb2312ToUtf8(splitlist[i])
        splitlist[i]=bs.PIN_MAP(splitlist[i])
    end 
    sys.publish(unpack(splitlist))
    sendQueue = {}
    sys.publish("DISPLAY",str)
    sys.publish("NET_RAW_MINO",str)
    sys.publish("COM",str.."->OK")
end)

sys.subscribe("NET_RAW_MONI", function(msg)
    sys.publish("NET_SENT_RDY_1",msg)
end)

硬件:

iRTU硬件仅占用GPIO4和Modem,其中GPIO4用于网络状态显示,Modem用于和服务器建立连接

完成后的代码,已经上传到了gitee,如果您刚好手头有我做的核心板,那就赶紧动手试试吧,即烧即用。 淘宝购买链接

https://item.taobao.com/item.htm?spm=a230r.1.14.40.b9cf2b69J1ppA7&id=624610770134&ns=1&abbucket=8#detail

菜鸟学写Cat1 Demo集(连载)
菜鸟学写Cat1 Demo集(十七) 提取iRTU内核 2020-09-08

八卦

我上一轮创业的失败因素很多,我觉得身体是其中最重要的一条。 很多年轻的朋友,都喜欢熬夜加班,结果二十几岁,就把身体搞垮了。 以我个人的经验,没有比这更亏的了。 因为无论你做任何决策,开发任何项目,对于一个创业者,你都是最重视这个项目的人,一定投入的精力远远大于周围的人。 但如果你身体不好,整个团队就会随着你虚弱的身体,变得拖拖沓沓,这样的团队不失败才怪。

我今天就属于这么个情况,胃疼了一天,代码基本上没写多少,别说创业,讲真打个工看来都难。我应该认清现实,努力存活,而不是坚持创业。

代码剥离

大家都知道iRTU固件是MIT协议的开源固件,因此我们每个人都可以下载到他的源码,并按照自己的项目进行修改。 我的想法现在是要把iRTU集成到我的DEMO集里面作为网络内核,那么第一个步骤就是要把iRTU进行精简,嵌入到现有项目中。

今天我只做了一点点,就是把cat1的内核文件从iRTU项目中分选出来,并且加上了一个适配文件。 具体怎么做的,等明天我身体好一点再跟大家讲,今天先贴代码

--IRTU配置文件 irtu.json

{
    "fota": 0,
    "uartReadTime": "25",
    "flow": "",
    "param_ver": "1",
    "pwrmod": "normal",
    "password": "",
    "netReadTime": "0",
    "passon": 1,
    "nolog": "0",
    "plate": 0,
    "reg": 0,
    "convert": 0,
    "uconf": [
        [],
        []
    ],
    "conf": [
        [
            "udp",
            "005501B01000000000100000000000000011234iRTUUPWs 1.0.000",
            10,
            "box.miuser.net",
            7101,
            1,
            "",
            "",
            ""
        ],
        [],
        [],
        [],
        [],
        [],
        []
    ],
    "preset": {
        "number": "",
        "delay": "",
        "smsword": ""
    },
    "apn": [
        "",
        "",
        ""
    ],
    "cmds": [
        [],
        []
    ],
    "pins": [],
    "gps": {
        "pio": [],
        "fun": []
    },
    "upprot": [
        "\nfunction\nlocal str=...\nlocal totallen=41+str:len()\nlocal ret=string.format(\"%04d\",totallen)..\"01\"..\"C\"..\"01\"..\"0000000001\"..\"0000000000000001\"..\"1234\"..str..\"05\"\nreturn ret\nend",
        "",
        "",
        "",
        "",
        "",
        ""
    ],
    "dwprot": [
        "function\nlocal str=...\nlocal ret=str:sub(40,-3)\nreturn ret\nend",
        "",
        "",
        "",
        "",
        "",
        ""
    ],
    "warn": {
        "adc0": [],
        "adc1": [],
        "vbatt": [],
        "gpio": []
    }
}

--适配文件 irtu_adapter.lua

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:iRTU固件适配模块
-- @author miuser
-- @module midemo.irtu_adapter
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-08
--------------------------------------------------------------------------
--iRTU的缺省配置文件,描述了首次烧写后的iRTU行为
CONFIG="irtu.json"


local function readfile(filename)--打开指定文件并输出内容

    local filehandle=io.open(filename,"r")--第一个参数是文件名,第二个是打开方式,'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then          --判断文件是否存在
        local fileval=filehandle:read("*all")--读出文件内容
      if  fileval  then
           print(fileval)  --如果文件存在,打印文件内容
           filehandle:close()--关闭文件
           return fileval
      else 
           print("The file is empty")--文件不存在
      end
    else 
        print("文件不存在或文件输入格式不正确") --打开失败  
    end    
end


local function writevalw(filename,value)--在指定文件中添加内容
    local filehandle = io.open(filename,"w")--第一个参数是文件名,后一个是打开模式'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then
        filehandle:write(value)--写入要写入的内容
        filehandle:close()
    else
        log.info("midemo.irtu_adapter","irtu适配文件不存在或文件输入格式不正确") 
    end
end
CONFIG="/lua/"..CONFIG

if not io.exists("/CONFIG.cnf") then
    content=readfile(CONFIG)
    writevalw("/CONFIG.cnf",content)
end

菜鸟学写Cat1 Demo集(十六) 网络功能

八卦一

今天秦总的大作《我的物联网大学》又更新了… 我拜读了两遍。 秦总每次写文章,并不是平铺直叙的讲道理,而是通过小故事的形式先吸引读者的兴趣,然后再把观点通过故事情节的方式展现出来,寓教于乐,所以我每次都读的津津有味。 今天秦总的小数是从武侠小说起始的,从《射雕》里面的郭靖一直讲到了《屠龙记》的张无忌。 讲真,过去武侠小说,我都没看过,儿时的时光通通都交给了《无线电》和《电子报》,所以到现在仍然是一个普普通通的工程师,而秦总则成就了庞大的合宙物联网产业集团… 真是少时不读金庸,长大注定平庸!

小的时候常听老人讲说,“年轻人知识面要广泛”,现在才开始领悟其中要害–古代的先贤也是都把道理通过故事的形式讲给普罗大众。 这样多读小说,从小耳濡目染,不自然的天分高人就一等。 我现在有多么后悔,当初痴迷于技术呀! 可是悔之晚矣,如果您还年轻,可千万别像我一样,回去赶紧补一下《金庸传》吧,这样看《我的物联网大学》才不会像我一样一脸懵逼。

八卦二,网络功能

讲真模块挺贵的,Air724的批量价格听说还要四十块呢。 如果不是因为有通讯功能,我想没人会买这么贵的MCU。 所以架子搭好以后,我首选想到的就是通信功能要尽快加上。 所谓网络功能,我想多白话几句。

最早的物联网模块网络方面一般就支持两种连接方式: TCP+IP, UDP+IP 这两种方式差不多涵盖了99%的物联网应用,后来的各种物联网协议,比如MQTT,COAP协议,还有各种的云,从本质上都是基于以上这两种通讯模式,属于更高层次的协议。至少到现在为止还没有看出任何一个协议有要脱离这两种协议独立组网的趋势。 但是众所周知,用沙子盖高楼并不现实。 所以现在的模块商为了使用户的使用更方便,普遍内置了MQTT,以及各种云的连接代码,并进行了封装,这样通常通过比较简单的代码就可以实现联网功能了。 这方面的功能,在官方demo中都有详细的介绍。

稀饭放姜(简称稀饭)开发了一款固件叫iRTU,也叫smartDTU 是一个专注于网络透传的开源固件。 稀饭老师通过卓越的努力,把我上面讲到的各种云,各种网络接口又进行了进一步的封装,使网络开发难度进一步的下降,现在只需要点几下鼠标就可以配置上机稳定使用,真的是简单到家了。 所以我在写DEMO的时候就在考虑这么一个超近的办法-与其自己费劲编写网络连接代码,还不如人家写的好。不如直接照抄稀饭老师的代码,谁叫他是MIT开源协议呢,嘿嘿,别怪我擅自剽窃,嘿嘿嘿。

但稀饭老师的iRTU固件虽好,但是想打这个的主意也绝非易事。 原因有二

一 稀饭老师的代码属于非常典型的极客风,使用了一些lua语言的高级用法,语法简练。但因此对于我这样的菜鸟读起来是非常的吃力的。

二 众所周知,luatask代码是稀饭老师写的,但是在cat1时代,稀饭老师可能是出于性能方面的考量为iRTU固件量身定做了Luatask3.0版本,但官方使用的稳定task版本仍然是V2.3.4,这也就是说,如果我使用V2.3.4的官方luatask框架,就无法直接使用iRTU固件源码,反过来,如果我用稀饭老师写的V3.0的Luatask框架,就没法保证官方的Demo能运行…这真是两难。

讲真,这个问题我也没有太好的办法,只能先硬着头皮上着试试,遇到问题再说。 按照官方网络部分的DEMO来弄固然可以,但是我更青睐稀饭老师经过千锤百炼的IRTU固件,所以思考再三。我还是决定迁移到Luatask3.0框架来,实在走不通再考虑回到官方的V2.3.4框架上来。

实施步骤

一 即使使用Luatask3.0,想把iRTU固件和我前面写的这些代码整合起来也并非易事,因为讲真,我一直也没有吃透稀饭老师的iRTU固件代码。

二 IRTU固件从用户接口上说,分为几个部分

菜鸟学写Cat1 Demo集(连载)

配置文件user.cfg.json ,这有点类似于我写的bs.lua,他描述了iRTU的工作方式,比如连接什么云,上下行数据如何处理等等。

核心文件只有两个,第一个“create.lua”,负责连接各种网络的功能。 第二个“default.lua”,负责模块本地初始化和各种硬件资源的控制,“tracker.lua”和“iic.lua”我的理解是这是两个外挂模块,负责GPS和I2C传感器。

对外iRTU固件的接口非常简练,占用的硬件资源最小化时只有硬件串口。

因此对iRTU的整合虽然可能,但并非毫无希望。 我们需要做的是对iRTU固件进行适配。 通过定义一个 irtu_adapter.lua的文件,将iRTU源码进行打包,作为其中一个组建即可。

irtu_adapter.lua 主要负责三件事

一,对iRTU已经初始化的硬件资源进行释放,避免重复初始化带来的冲突

二,对iRTU的通讯功能与demo集进行消息耦合,提供联网功能。

三,增设一组新的指令,用以对iRTU固件进行遥控控制

基本规划就酱紫了,后面就要开始啃iRTU的代码了,只有读懂了iRTU代码,后面的整合工作才有希望。 这个工作对我而言非常具有挑战性。 失败几率很大,先试试吧。 反正失败了,我也没啥损失,再找其他的路。

菜鸟学写Cat1 Demo集(十五) 核心板适配编码

八卦

讲真,我这个人原来最喜欢挑别人毛病了,看别人都是这不顺那不顺,这不对,那不行。 现在不是了,看谁都挺好。 我几乎从每个人身上都能找到胜过自己的地方,比如有的人脾气比我好,有的人技术比我强,有的人善演讲,有的人能预测。 一句话讲,能在中国这片土地上存活下来,个个都有绝活。 不然早就饿死了。 你可能会说,有的人啥也不会,也没啥优点,还位高权重,特别有钱,那这种人就更厉害了,我给你讲。 说明人家一定是人品好,这个东西你想学都学不来。 学不走的本事才是真正的核心技术。 就好比说你办一家公司,公司又不止一个人,肯定不能啥事你都瞒着大伙吧,总有不少人知道公司的运作方式。 如果你运作的方式方法别人都学得来,那人家也可以复制一家和你一样的公司,把你的饭碗抢走,对吧。 所以凡是可以复制的东西,都不是核心技术。 人品这东西就不好复制,人品突然发作,没准直接摸个七星彩头等奖呢,直接一部腾飞啦!

昨天说到核心板定义,今天的任务就是落实到纸面上,编写bs.lua,下面先直接上代码,我再解释

代码

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected],如有侵权嫌疑将立即纠正
---------------------------------------------------------------------------------------------------------------------------------------
--- 模块功能:核心板适配文件,适配硬件版本Cat1 Phone Core V2b
-- @author miuser
-- @module midemo.bs
-- @license MIT
-- @copyright [email protected]
-- @release 2020-09-05
--------------------------------------------------------------------------
-- @说明部分
--------------------------------------------------------------------------

-- 引脚       名称      功能
-- B1       GND         电源负极
-- B2       4V IN       锂电池供电输入
-- B3       5V IN       5V直流电源输入
-- B4       UART1_TX    串口1发送 
-- B5       UART1_RX    串口1接收 
-- B6       GPIO19      双向输入输出IO
-- B7       GPIO18      双向输入输出IO
-- B8       RESET       复位
-- B9       POWER ON    电源按键
-- B10      UART2_TX    串口2发送
-- B11      UART2_RX    串口2接收
-- B12      GPIO13      不建议使用
-- B13      1.8V OUT    1.8V供电输出
-- B14      MIC+        麦克风正极
-- B15      MIC-        麦克风负极
-- B16      SPK-        喇叭负极
-- B17      SPK+        喇叭正极
-- A1       GND         电源负极
-- A2       SCL         I2C总线时钟
-- A3       SDA         I2C总线数据
-- A4       SPI_CS      SPI总线片选
-- A5       SPI_CLK     SPI总线时钟
-- A6       SPI_MOSI    SPI总线数据输出
-- A7       SPI_MISO    SPI总线数据输入
-- A8       GND         显示屏供电负极
-- A9       VLCD        显示屏供电正极
-- A10      LCD_CK      显示屏时钟
-- A11      LCD_DA      显示屏数据
-- A12      LCD_RST     显示屏复位
-- A13      LCD_DC      显示屏命令数据切换
-- A14      LCD_CS      显示屏片选
-- A15      ADC3        模拟输入3
-- A16      ADC2        模拟输入2
-- A17      GND         电源负极
-- C1       GND         电源负极
-- C2       D-          USB差分数据负极
-- C3       D+          USB差分数据正极
-- C4       UART3_TX    串口3发送
-- C5       UART3_RX    串口3接收
-- C6       5V IN       5V直流电源输入

--------------------------------------------------------------------------
-- @用户可修改部分
--------------------------------------------------------------------------
----------------------引脚分配表-----------------------------
------------------------------------------------------------
------------------------------------------------------------
module(...,package.seeall)
-- 将串口1,2,3及USB虚拟串口分配作为命令控制接口
COM_UART_IDs={1,2,3,129}  
--GPIO13,18,19映射为双向IO端口
BIOPins={13,18,19}
------------------------------------------------------------


------------------------------------------------------------
----------------------引脚映射表-----------------------------
------------------------------------------------------------
MAP={}
--核心板的B6引脚映射为GPIO19
MAP["B6"]="19"
--核心板的B7引脚映射为GPIO18
MAP["B7"]="18"
--核心板的B12引脚映射为GPIO13,这个引脚为内置LED使用
MAP["B12"]="13"
------------------------------------------------------------

---------以下内容为内部函数,不需要用户修改--------------------

--查找输入的参数是否存在映射替换
function PIN_MAP(boardpin)
   if (MAP[boardpin]~=nil) then 
        return MAP[boardpin]
   else
        return boardpin
   end
end

文件您也看到了,注释比代码多,其实这是正常的。 特别是这种定义的文件,其实作用有二

一是要运行,代码肯定得能跑对吧,这不用说

二是要好读,因为这个牵涉到功能的分配,和引脚的定义,客人极有可能是要手工修改的。 整个系统毕竟不是客人开发的,也不可能为了改一个配置把整个源码读一遍,所以这时候说明就显得特别的关键。

下面我简单介绍下:

这个文件最重的是两部分

第一部分是引脚分配表,规定了各个功能模块分配的的功能

第二部分是引脚映射表,约定了核心板引脚和内部功能模块之间的连接关系

这两个部分未来随着新的功能模块逐渐的加进来也会继续增加。

#运行效果

下面是运行效果,我们使用晨旭大佬的写的LLCOM作为调试工具,后面也会尝试根据我们的需要对该工具进行一些相应的配置定制,因为LLCOM开源的嘛,建议您也来三连一下地址如下:
https://gitee.com/chenxuuu/llcom

菜鸟学写Cat1 Demo集(连载)

我们可以看到B12已经被映射为GPIO13,达到了预期的效果。 B12是开发板的真实引脚,也就说客人使用开发板无需关注板子的设计,只要知道核心板外部引脚号就可以对电路进行控制了。
当然我写的这个都是DEMO,真正的BSP要复杂的多,但是作用是大同小异的,都是为了将应用接口和内部的物理原件隔离开,从而方便进行代码移植,今天就酱紫了,谢谢大家。 现在您理解BSP的作用了么?

菜鸟学写Cat1 Demo集(十四) 核心板适配定义

八卦

中东西方文化是有差别的,东方人含蓄,西方人更简洁。 作为熟悉西方那一套工作方式的人,撂地单干肯定是要吃亏的。 我头几年就栽了大跟头。 现在想想这纯属活该。 中国文化讲究的是虚虚实实,画龙点睛。 所以我写的这个文章也是这么个意思。 如果把开发工作比作一副东方山水画,我这做的就是那个背景,朦朦胧胧的山水那种,其中的亭台楼阁,画龙点睛之笔就得靠您多帮忙了。

昨天讲到核心板的适配,我要做的事儿其实就是让命令更加的简洁直观。 具体下来有几点是我要做的。

1)对引脚功能进行专一化,单一引脚分配唯一功能

2)将DEMO的控制指令简化为对板子各功能接口的控制,而无需用户关心太多的实现机制(当然想关心也可以,我开源的嘛)

要完成这两点,我的策略就是制作一个表单,内容包含如下

1) 各个端口的功能分配

2) 名称映射

对于Phone core 核心板,我希望这个定义对于入门用户是确切的,唯一的,避免发生混淆。 下面是我的定义图

核心板定义(pin map)

菜鸟学写Cat1 Demo集(连载)

考虑到这个项目主要是一个DEMO项目,因此我尽量在板子的引脚上多实现一些功能。 等您熟悉了以后,可以根据自己的需要再做裁剪。 功能多了可以往下裁剪比较容易,但功能少了,自己写可就累了,是这个理儿吧。

下面是定义表:

菜鸟学写Cat1 Demo集(连载)

由于为了集成更多的功能,所以剩下的GPIO就比较少了,只有两个分别为B6,B7,对应GPIO19,GPIO18。 其实A10-A14也是很好的GPIO口,特别是可以用指令进行调节适配3.3V电平的单片机,这个就需要您自己动手去改动配置了。 我相信这个时候愿意读我文章的人,您一定也是一个崇尚自己动手丰衣足食的人,所以这个事儿您一定不在话下。

适配文件 bs.lua

我们做完定义以后,也就要有相应的适配文件做出来,也就是bs.lua,这也不是高大上的东西,其实就是一个查表进行匹配的工作。 今天不写代码,光说说原理:

比如说您希望把GPIO19设置为高电平,您需要输入如下指令

SETGPIO,19,1 --这里的19是GPIO号,是与模块相关的,我们希望他和我们的板子相关,而无需知道模块的定义,所以我们就建立一个表格,把“19” 映射为B6,再说白点,就是查表发现参数里有B6,就替换成数字19,这样讲够简单直接的吧。

所以,增加适配文件后,您输入

SETGPIO,B6,1 --就可以把核心板的B6引脚至高。 引脚即功能定义是我设计的初衷。

同理,如果我们希望测量A15引脚的电压

GETVOLTAGE,A15 --就可以测量出来A15的电压了

查表替换这个功能,我暂时还不会,明天学习下开始写代码,今天就到这里了,感谢您的阅读!

菜鸟学写Cat1 Demo集(十三)核心板适配

八卦

今天吹吹我的手工水平,你们可能不知道,我在上学那会手是相当的巧。 有多巧呢,军工实习课,老师要求用粗铜丝做一个小青蛙,我是全班第一个做完了,得了60分! 对您没听错,是60分,一分不多一分不少,没一点糟践,刚好够用。 我给你讲,一般人不容易正正好好得这个分数。 老师给我这个分数有两个原因: 一,这个青蛙真正的极简风,多么极简呢,我根据当初的记忆手绘一下子。

菜鸟学写Cat1 Demo集(连载)

二,当时我们上课时老师发的是一种很粗很难用的焊锡丝,我改用了我自己找来的细细的那种多芯松香焊锡丝,焊接特别的666。所以才能抢先别人数倍的时间完成,哈哈,老师也是哭笑不得。毕竟是完成任务了,所以给了60分。

现在想起当年做的这些个糗事,真的很傻。 老师安排这样的物料意义在于锻炼我们的能力,我图捷径,最后手艺确不怎么长进。 本末倒置,坑的是自己。 现在再也不敢偷懒了。 但一切都晚了….

BSP

我决定不改我一如既往的装13调调,先抛出一个高大上的名字:BPS,全称是Board Support package 百度的。 其实严格意义上我,我一个DEMO,又不是操作系统,也不分啥层。所以不配叫BSP。 但是我就借用一下,装装哈。

其实我要做的是这么一个事儿, 对于GPIO,合宙官方有两种标注方法,最简单的是直接写输入,比如GPIO0,就写数字0就可以,GPIO1,就写数字1,如此类推。 但是如果你想装一下,也可以写P0.0其实是一个意思。 下面废话少说,直接上图

菜鸟学写Cat1 Demo集(连载)

很遗憾,我手太慢,没画完,您先看个意思。也可以猜猜我的用意。明天继续。 祝大家周末愉快
菜鸟学写Cat1 Demo集(十二)完成OLED驱动

八卦

有时候,人是会走狗屎运的,昨天我就交了狗屎运,今天一大早把代码烧进去,接上屏幕,零bug过关,这真是令我喜出望外。

今天在群里有大佬聊到 人工智能的本质,有的人觉得人和代码是差不多的,都是按照逻辑一步一步的工作。这个观点我显然是不能苟同的。 因为我大多数时候都不按逻辑做事,至少电脑不会像我一样,坏了除外…

代码逻辑

现在就呈现上完成后的效果如下:

菜鸟学写Cat1 Demo集(连载)

我把屏幕分成了4行,点阵为8X6,其中包含横纵各一个空行/列,所以字模的实际长宽为7*5,第一行显示联网情况和信号质量,第二行是分隔符,第三行和第四行滚动显示串口输入命令的回显,其实就是输入啥就再啥后面加一个OK,嘿嘿,简单吧。

信号质量是通过一个timer来不断刷新的,这样一则可以随时监测网络连接情况,另一方面也能通过这个判断是否死机。今天就到这里了,最后奉上代码,请自行阅读,最后再啰嗦一遍,这个代码,我自己并不会从头写,我是按陈夏大佬的DEMO改过来的,再次严重感谢。

代码分析

--- 模块功能:SSD 1306驱动芯片 I2C屏幕显示128X32点阵英文字符
-- @original author CX
-- @release 2020.02.23
-- @modified by miuser
-- @release 2020.09.03
-- @module midemo.oled
-- @license MIT


module(..., package.seeall)
require "bit"

local lib_5X7={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x14,0x7F,0x14,0x7F,0x14,0x24,0x2A,0x07,0x2A,0x12,0x23,0x13,0x08,0x64,0x62,0x37,0x49,0x55,0x22,0x50,0x00,0x05,0x03,0x00,0x00,0x00,0x1C,0x22,0x41,0x00,0x00,0x41,0x22,0x1C,0x00,0x08,0x2A,0x1C,0x2A,0x08,0x08,0x08,0x3E,0x08,0x08,0x00,0x50,0x30,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x60,0x60,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x3E,0x51,0x49,0x45,0x3E,0x00,0x42,0x7F,0x40,0x00,0x42,0x61,0x51,0x49,0x46,0x21,0x41,0x45,0x4B,0x31,0x18,0x14,0x12,0x7F,0x10,0x27,0x45,0x45,0x45,0x39,0x3C,0x4A,0x49,0x49,0x30,0x01,0x71,0x09,0x05,0x03,0x36,0x49,0x49,0x49,0x36,0x06,0x49,0x49,0x29,0x1E,0x00,0x36,0x36,0x00,0x00,0x00,0x56,0x36,0x00,0x00,0x00,0x08,0x14,0x22,0x41,0x14,0x14,0x14,0x14,0x14,0x41,0x22,0x14,0x08,0x00,0x02,0x01,0x51,0x09,0x06,0x32,0x49,0x79,0x41,0x3E,0x7E,0x11,0x11,0x11,0x7E,0x7F,0x49,0x49,0x49,0x36,0x3E,0x41,0x41,0x41,0x22,0x7F,0x41,0x41,0x22,0x1C,0x7F,0x49,0x49,0x49,0x41,0x7F,0x09,0x09,0x01,0x01,0x3E,0x41,0x41,0x51,0x32,0x7F,0x08,0x08,0x08,0x7F,0x00,0x41,0x7F,0x41,0x00,0x20,0x40,0x41,0x3F,0x01,0x7F,0x08,0x14,0x22,0x41,0x7F,0x40,0x40,0x40,0x40,0x7F,0x02,0x04,0x02,0x7F,0x7F,0x04,0x08,0x10,0x7F,0x3E,0x41,0x41,0x41,0x3E,0x7F,0x09,0x09,0x09,0x06,0x3E,0x41,0x51,0x21,0x5E,0x7F,0x09,0x19,0x29,0x46,0x46,0x49,0x49,0x49,0x31,0x01,0x01,0x7F,0x01,0x01,0x3F,0x40,0x40,0x40,0x3F,0x1F,0x20,0x40,0x20,0x1F,0x7F,0x20,0x18,0x20,0x7F,0x63,0x14,0x08,0x14,0x63,0x03,0x04,0x78,0x04,0x03,0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x7F,0x41,0x41,0x02,0x04,0x08,0x10,0x20,0x41,0x41,0x7F,0x00,0x00,0x04,0x02,0x01,0x02,0x04,0x40,0x40,0x40,0x40,0x40,0x00,0x01,0x02,0x04,0x00,0x20,0x54,0x54,0x54,0x78,0x7F,0x48,0x44,0x44,0x38,0x38,0x44,0x44,0x44,0x20,0x38,0x44,0x44,0x48,0x7F,0x38,0x54,0x54,0x54,0x18,0x08,0x7E,0x09,0x01,0x02,0x08,0x14,0x54,0x54,0x3C,0x7F,0x08,0x04,0x04,0x78,0x00,0x44,0x7D,0x40,0x00,0x20,0x40,0x44,0x3D,0x00,0x00,0x7F,0x10,0x28,0x44,0x00,0x41,0x7F,0x40,0x00,0x7C,0x04,0x18,0x04,0x78,0x7C,0x08,0x04,0x04,0x78,0x38,0x44,0x44,0x44,0x38,0x7C,0x14,0x14,0x14,0x08,0x08,0x14,0x14,0x18,0x7C,0x7C,0x08,0x04,0x04,0x08,0x48,0x54,0x54,0x54,0x20,0x04,0x3F,0x44,0x40,0x20,0x3C,0x40,0x40,0x20,0x7C,0x1C,0x20,0x40,0x20,0x1C,0x3C,0x40,0x30,0x40,0x3C,0x44,0x28,0x10,0x28,0x44,0x0C,0x50,0x50,0x50,0x3C,0x44,0x64,0x54,0x4C,0x44,0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x41,0x36,0x08,0x00,0x02,0x01,0x02,0x04,0x02,0xff,0xff,0xff,0xff,0xff}
local i2cid = 0
local i2cslaveaddr = 0x3c
-- 宏定义
local OLED_CMD = 0 -- 命令
local OLED_DATA = 1 -- 数据
local SIZE = 16 --显示字符的大小
local Max_Column = 128 --最大列数
local Max_Row = 64 --最大行数
local X_WIDTH = 128 --X轴的宽度
local Y_WIDTH = 64 --Y轴的宽度

-- 向OLED写入指令字节
function OLED_Write_Command(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x00, OLED_Byte})
end

-- 向OLED写入数据字节
function OLED_Write_Data(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x40, OLED_Byte})
end


-- 向OLED写入一字节数据/指令
function OLED_WR_Byte(OLED_Byte, OLED_Type)
    if OLED_Type == OLED_DATA then
        OLED_Write_Data(OLED_Byte)--写入数据
    else
        OLED_Write_Command(OLED_Byte)--写入指令
    end
end

-- 整屏写入某值
function OLED_Clear()
    local N_Page, N_row = 0, 0
    for N_Page = 1, 8 do
        OLED_WR_Byte(0xb0 + N_Page - 1, OLED_CMD)-- 从0~7页依次写入
        OLED_WR_Byte(0x00, OLED_CMD)-- 列低地址
        OLED_WR_Byte(0x10, OLED_CMD)-- 列高地址
        for N_row = 1, 128 do
            OLED_WR_Byte(0x00, OLED_DATA)
        end
    end
end

-- 设置数据写入的起始行、列
function OLED_Set_Pos(x, y)
    OLED_WR_Byte(0xb0 + y, OLED_CMD)-- 写入页地址
    OLED_WR_Byte(bit.band(x, 0x0f), OLED_CMD)-- 写入列的地址(低半字节)
    OLED_WR_Byte(bit.bor(bit.rshift(bit.band(x, 0xf0), 4), 0x10), OLED_CMD)-- 写入列的地址(高半字节)
end

--初始化OLED
-------------------------------------------------------------------------------
function OLED_Init()
    --i2c.set_id_dup(0)
    if i2c.setup(i2cid, i2c.SLOW) ~= i2c.SLOW then
        print("testI2c.init fail")
        return
    end
    pmd.ldoset(4,pmd.LDO_VLCD)
    --OLED_WR_Byte(0xAE, OLED_CMD)-- 关闭显示

    OLED_WR_Byte(0x00, OLED_CMD)-- 设置低列地址
    OLED_WR_Byte(0x10, OLED_CMD)-- 设置高列地址
    OLED_WR_Byte(0x40, OLED_CMD)-- 设置起始行地址
    OLED_WR_Byte(0xB0, OLED_CMD)-- 设置页地址

    OLED_WR_Byte(0x81, OLED_CMD)-- 对比度设置,可设置亮度
    OLED_WR_Byte(0xFF, OLED_CMD)-- 265

    OLED_WR_Byte(0xA1, OLED_CMD)-- 设置段(SEG)的起始映射地址
    OLED_WR_Byte(0xA6, OLED_CMD)-- 正常显示;0xa7逆显示

    OLED_WR_Byte(0xA8, OLED_CMD)-- 设置驱动路数(16~64)
    OLED_WR_Byte(0x1F, OLED_CMD)-- 64duty

    OLED_WR_Byte(0xC8, OLED_CMD)-- 重映射模式,COM[N-1]~COM0扫描

    OLED_WR_Byte(0xD3, OLED_CMD)-- 设置显示偏移
    OLED_WR_Byte(0x00, OLED_CMD)-- 无偏移

    OLED_WR_Byte(0xD5, OLED_CMD)-- 设置震荡器分频
    OLED_WR_Byte(0x80, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xD9, OLED_CMD)-- 设置 Pre-Charge Period
    OLED_WR_Byte(0xF1, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0xDA, OLED_CMD)-- 设置 com pin configuartion
    OLED_WR_Byte(0x02, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xDB, OLED_CMD)-- 设置 Vcomh,可调节亮度(默认)
    OLED_WR_Byte(0x40, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0x8D, OLED_CMD)-- 设置OLED电荷泵
    OLED_WR_Byte(0x14, OLED_CMD)-- 开显示

    OLED_WR_Byte(0xAF, OLED_CMD)-- 开启OLED面板显示

    --OLED_Clear()-- 清屏

    OLED_Set_Pos(0, 0)-- 设置数据写入的起始行、列
end

--在指定坐标处显示一个字符
-------------------------------------------------------------------------------
function OLED_ShowChar(x, y, Show_char,isChinese)
    local c, i = 0, 0

    if x > Max_Column - 1 then
        x = 0
        y = y + 2
    end -- 当列数超出范围,则另起2页

    c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量

    -- 画第一页
    OLED_Set_Pos(x, y)-- 设置画点起始处
    for i = 1, 5 do -- 循环5次(5列)
        OLED_WR_Byte(lib_5X7[5*c+i], OLED_DATA)-- 找到字模
    end

end

-- 在指定坐标起始处显示字符串
function OLED_ShowString(x, y, ShowStr)

    local len = #ShowStr
    local N_Char=1

    while N_Char <= len do
        local char_l=string.char(string.byte(ShowStr, N_Char))
        local char_r=""
        if (N_Char<len) then
            char_r=string.char(string.byte(ShowStr, N_Char+1))
        else
            char_r=" "
        end

        --log.info("OLED_ShowChar",len,char_l:toHex().." "..char_r:toHex())

        if (char_l:byte()<0xA1) then
            OLED_ShowChar(x, y,char_l,nil)-- 显示一个英文字符
        else
            OLED_ShowChar(x, y,"*",nil)-- 显示一个英文字符
        end
        x = x + 6 -- 列数加6,一个字符占6列
        if x >= 126 then
            x = 0
            y = y + 1
        end -- 当x>=128,另起一页

        N_Char=N_Char+1

    end
end


-- 显示模块的测试
s='-'
OLED_Init()
OLED_Clear()
OLED_ShowString(0, 0, "  Network RSSI: ")
OLED_ShowString(0, 1, "---------------------")
sys.timerLoopStart(function()
    --oled.OLED_Init()
    rssi=net.getRssi()
    log.info("Displaying")
    if (net.getState()=="REGISTERED") then 
        OLED_ShowString(95, 0,tostring(rssi).." "..s)
        if (s=="-") then s=[[\]] else
            if (s==[[\]]) then s="/" else
                    if (s==[[/]]) then s="-" end
            end
        end
    else
        OLED_ShowString(95, 0,"NA "..s)
    end
    --i2c.close(0)
 end,100)

laststr=" "
--从系统消息接收主题为“DISPLAY”的消息,并转发到串口
local function disprsv(msg)
    blankcount=21-#msg
    for i=1,blankcount do
        msg=msg.." "
    end
    oled.OLED_ShowString(0,2,laststr)
    oled.OLED_ShowString(0,3,msg)
    laststr=msg
end
sys.subscribe("DISPLAY", disprsv)

菜鸟学写Cat1 Demo集(十一)显示驱动修改 2020-09-02

八卦

人写程序的时候其实也有二八定律,20%的时间写80%的代码,20%的代码却要花掉80%的时间。 无比聪明的你一定想说,能不能光要前一半,其实这并不能。 这就好像是往一个池子里塞石子,最后总是要有填缝的时候,而这个时候是快不了的。 而作为代码,少一行代码也跑不好,早晚会出问题。 讲了这么一堆,其实我是给自己找写代码慢的理由。

驱动修改

昨天讲到,要想把这个128X32的屏幕改为四行显示,有两个关键因素。 今天就都来实现一下

1) 需要有5X7的字库,最终占用空间是6X8,因为底部和右侧要留字符与字符的间隔,我在网上废了好大的力气,才找到了一个C语言写的,我把他改成了lua的结构,其实就是一个二维表,第一维是点阵列,第二位是点阵行,然后按ASCII码的次序进行排列索引。 另外就是把原来16X16的字符改为 6X8的显示阵列。 下面是改完以后的代码。 可见字符是0X20开始的,lib_5X7是从0x20起步,对字符进行存储,第一个字符是空字符,第二个字符是“!”,最后一个字符是全黑字符。

我们以显示!为例,!对应的ASCII码为0X21,对应我们的lua字库表,的第6-10个元素,为0x00,0x00,0x5F,0x00,0x00 0x5F就是叹号的形状,低位在上,高位在下。 我们读取到后通过OLED_WR_Byte(value, OLED_DATA)函数烧写到显存里,就这么简单。 具体成不成功,我得回去再试验,因为今天为了要见一位很重要的大佬,所以出差在外,不方便调试。 所以只能光说不练了。 不过代码基本上写完了,您有兴趣可以先试试,然后把试验结果告诉我,等于帮我调试了,我感激不尽呀!

代码分析

--- 模块功能:SSD 1306驱动芯片 I2C屏幕显示测试
-- @original author CX
-- @module midemo.oled
-- @license MIT
-- @release 2020.02.23

module(..., package.seeall)
require "bit"

local lib_5X7={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x14,0x7F,0x14,0x7F,0x14,0x24,0x2A,0x07,0x2A,0x12,0x23,0x13,0x08,0x64,0x62,0x37,0x49,0x55,0x22,0x50,0x00,0x05,0x03,0x00,0x00,0x00,0x1C,0x22,0x41,0x00,0x00,0x41,0x22,0x1C,0x00,0x08,0x2A,0x1C,0x2A,0x08,0x08,0x08,0x3E,0x08,0x08,0x00,0x50,0x30,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x60,0x60,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x3E,0x51,0x49,0x45,0x3E,0x00,0x42,0x7F,0x40,0x00,0x42,0x61,0x51,0x49,0x46,0x21,0x41,0x45,0x4B,0x31,0x18,0x14,0x12,0x7F,0x10,0x27,0x45,0x45,0x45,0x39,0x3C,0x4A,0x49,0x49,0x30,0x01,0x71,0x09,0x05,0x03,0x36,0x49,0x49,0x49,0x36,0x06,0x49,0x49,0x29,0x1E,0x00,0x36,0x36,0x00,0x00,0x00,0x56,0x36,0x00,0x00,0x00,0x08,0x14,0x22,0x41,0x14,0x14,0x14,0x14,0x14,0x41,0x22,0x14,0x08,0x00,0x02,0x01,0x51,0x09,0x06,0x32,0x49,0x79,0x41,0x3E,0x7E,0x11,0x11,0x11,0x7E,0x7F,0x49,0x49,0x49,0x36,0x3E,0x41,0x41,0x41,0x22,0x7F,0x41,0x41,0x22,0x1C,0x7F,0x49,0x49,0x49,0x41,0x7F,0x09,0x09,0x01,0x01,0x3E,0x41,0x41,0x51,0x32,0x7F,0x08,0x08,0x08,0x7F,0x00,0x41,0x7F,0x41,0x00,0x20,0x40,0x41,0x3F,0x01,0x7F,0x08,0x14,0x22,0x41,0x7F,0x40,0x40,0x40,0x40,0x7F,0x02,0x04,0x02,0x7F,0x7F,0x04,0x08,0x10,0x7F,0x3E,0x41,0x41,0x41,0x3E,0x7F,0x09,0x09,0x09,0x06,0x3E,0x41,0x51,0x21,0x5E,0x7F,0x09,0x19,0x29,0x46,0x46,0x49,0x49,0x49,0x31,0x01,0x01,0x7F,0x01,0x01,0x3F,0x40,0x40,0x40,0x3F,0x1F,0x20,0x40,0x20,0x1F,0x7F,0x20,0x18,0x20,0x7F,0x63,0x14,0x08,0x14,0x63,0x03,0x04,0x78,0x04,0x03,0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x7F,0x41,0x41,0x02,0x04,0x08,0x10,0x20,0x41,0x41,0x7F,0x00,0x00,0x04,0x02,0x01,0x02,0x04,0x40,0x40,0x40,0x40,0x40,0x00,0x01,0x02,0x04,0x00,0x20,0x54,0x54,0x54,0x78,0x7F,0x48,0x44,0x44,0x38,0x38,0x44,0x44,0x44,0x20,0x38,0x44,0x44,0x48,0x7F,0x38,0x54,0x54,0x54,0x18,0x08,0x7E,0x09,0x01,0x02,0x08,0x14,0x54,0x54,0x3C,0x7F,0x08,0x04,0x04,0x78,0x00,0x44,0x7D,0x40,0x00,0x20,0x40,0x44,0x3D,0x00,0x00,0x7F,0x10,0x28,0x44,0x00,0x41,0x7F,0x40,0x00,0x7C,0x04,0x18,0x04,0x78,0x7C,0x08,0x04,0x04,0x78,0x38,0x44,0x44,0x44,0x38,0x7C,0x14,0x14,0x14,0x08,0x08,0x14,0x14,0x18,0x7C,0x7C,0x08,0x04,0x04,0x08,0x48,0x54,0x54,0x54,0x20,0x04,0x3F,0x44,0x40,0x20,0x3C,0x40,0x40,0x20,0x7C,0x1C,0x20,0x40,0x20,0x1C,0x3C,0x40,0x30,0x40,0x3C,0x44,0x28,0x10,0x28,0x44,0x0C,0x50,0x50,0x50,0x3C,0x44,0x64,0x54,0x4C,0x44,0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x41,0x36,0x08,0x00,0x02,0x01,0x02,0x04,0x02,0xff,0xff,0xff,0xff,0xff}
local i2cid = 0
local i2cslaveaddr = 0x3c
-- 宏定义
local OLED_CMD = 0 -- 命令
local OLED_DATA = 1 -- 数据
local SIZE = 16 --显示字符的大小
local Max_Column = 128 --最大列数
local Max_Row = 64 --最大行数
local X_WIDTH = 128 --X轴的宽度
local Y_WIDTH = 64 --Y轴的宽度

-- 向OLED写入指令字节
function OLED_Write_Command(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x00, OLED_Byte})
end

-- 向OLED写入数据字节
function OLED_Write_Data(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x40, OLED_Byte})
end


-- 向OLED写入一字节数据/指令
function OLED_WR_Byte(OLED_Byte, OLED_Type)
    if OLED_Type == OLED_DATA then
        OLED_Write_Data(OLED_Byte)--写入数据
    else
        OLED_Write_Command(OLED_Byte)--写入指令
    end
end

-- 整屏写入某值
function OLED_Clear()
    local N_Page, N_row = 0, 0
    for N_Page = 1, 8 do
        OLED_WR_Byte(0xb0 + N_Page - 1, OLED_CMD)-- 从0~7页依次写入
        OLED_WR_Byte(0x00, OLED_CMD)-- 列低地址
        OLED_WR_Byte(0x10, OLED_CMD)-- 列高地址
        for N_row = 1, 128 do
            OLED_WR_Byte(0x00, OLED_DATA)
        end
    end
end

-- 设置数据写入的起始行、列
function OLED_Set_Pos(x, y)
    OLED_WR_Byte(0xb0 + y, OLED_CMD)-- 写入页地址
    OLED_WR_Byte(bit.band(x, 0x0f), OLED_CMD)-- 写入列的地址(低半字节)
    OLED_WR_Byte(bit.bor(bit.rshift(bit.band(x, 0xf0), 4), 0x10), OLED_CMD)-- 写入列的地址(高半字节)
end


--初始化OLED
-------------------------------------------------------------------------------
function OLED_Init()
    --i2c.set_id_dup(0)
    if i2c.setup(i2cid, i2c.SLOW) ~= i2c.SLOW then
        print("testI2c.init fail")
        return
    end

    --OLED_WR_Byte(0xAE, OLED_CMD)-- 关闭显示

    OLED_WR_Byte(0x00, OLED_CMD)-- 设置低列地址
    OLED_WR_Byte(0x10, OLED_CMD)-- 设置高列地址
    OLED_WR_Byte(0x40, OLED_CMD)-- 设置起始行地址
    OLED_WR_Byte(0xB0, OLED_CMD)-- 设置页地址

    OLED_WR_Byte(0x81, OLED_CMD)-- 对比度设置,可设置亮度
    OLED_WR_Byte(0xFF, OLED_CMD)-- 265

    OLED_WR_Byte(0xA1, OLED_CMD)-- 设置段(SEG)的起始映射地址
    OLED_WR_Byte(0xA6, OLED_CMD)-- 正常显示;0xa7逆显示

    OLED_WR_Byte(0xA8, OLED_CMD)-- 设置驱动路数(16~64)
    OLED_WR_Byte(0x1F, OLED_CMD)-- 64duty

    OLED_WR_Byte(0xC8, OLED_CMD)-- 重映射模式,COM[N-1]~COM0扫描

    OLED_WR_Byte(0xD3, OLED_CMD)-- 设置显示偏移
    OLED_WR_Byte(0x00, OLED_CMD)-- 无偏移

    OLED_WR_Byte(0xD5, OLED_CMD)-- 设置震荡器分频
    OLED_WR_Byte(0x80, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xD9, OLED_CMD)-- 设置 Pre-Charge Period
    OLED_WR_Byte(0xF1, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0xDA, OLED_CMD)-- 设置 com pin configuartion
    OLED_WR_Byte(0x02, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xDB, OLED_CMD)-- 设置 Vcomh,可调节亮度(默认)
    OLED_WR_Byte(0x40, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0x8D, OLED_CMD)-- 设置OLED电荷泵
    OLED_WR_Byte(0x14, OLED_CMD)-- 开显示

    OLED_WR_Byte(0xAF, OLED_CMD)-- 开启OLED面板显示

    --OLED_Clear()-- 清屏

    OLED_Set_Pos(0, 0)-- 设置数据写入的起始行、列
end

--在指定坐标处显示一个字符
-------------------------------------------------------------------------------
function OLED_ShowChar(x, y, Show_char,isChinese)
    local c, i = 0, 0

    if x > Max_Column - 1 then
        x = 0
        y = y + 2
    end -- 当列数超出范围,则另起2页

    c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量

    -- 画第一页
    OLED_Set_Pos(x, y)-- 设置画点起始处
    for i = 1, 5 do -- 循环5次(5列)
        --log.info("ssd1306","left:",string.char(lib_5X7[Show_char-0x20+1], OLED_DATA):toHex())
        log.info("mod",lib_5X7[c+1],lib_5X7[c+2],lib_5X7[c+3],lib_5X7[c+4],lib_5X7[c+5])
        OLED_WR_Byte(lib_5X7[c*5+i], OLED_DATA)-- 找到字模
    end

end

-- 在指定坐标起始处显示字符串
function OLED_ShowString(x, y, ShowStr)

    local len = #ShowStr
    local N_Char=1

    while N_Char <= len do
        local char_l=string.char(string.byte(ShowStr, N_Char))
        local char_r=""
        if (N_Char<len) then
            char_r=string.char(string.byte(ShowStr, N_Char+1))
        else
            char_r=" "
        end

        --log.info("OLED_ShowChar",len,char_l:toHex().." "..char_r:toHex())

        if (char_l:byte()<0xA1) then
            OLED_ShowChar(x, y,char_l,nil)-- 显示一个英文字符
        end
        x = x + 6 -- 列数加6,一个字符占6列
        if x >= 128 then
            x = 0
            y = y + 2
        end -- 当x>=128,另起一页

        N_Char=N_Char+1

    end
end

--控制SPI引脚的电压域
pmd.ldoset(8,pmd.LDO_VLCD)

讲真,现在看到一个个大佬没黑没夜的满世界的跑,内心的感受是无比复杂的。 究竟科技是使人们变得更快乐了,还是更疲惫了,这的确是一个问题。 我开始怀念我上小学的时候了,那时候电脑的内存还是1MB… 软盘驱动器转起来还会吱吱的响,这声音萦绕在耳边,仿佛就是昨天。 电脑屏幕上的光标一闪一闪,仿佛是在召唤我…

菜鸟学写Cat1 Demo集(十)读懂显示接口 -2020-09-01

八卦

晚了,晚了,晚了,一切都晚了。 还有40分钟就过去了,可是今天的文章还没动笔。 很多事其实就是这样,当你的时间余额不足的时候,任凭你再努力也是没用的。 就像是期末考试,答题是那一小时,功夫却在过去的一年。

我现在唯一能做的就是利用好这40分钟,做一点稍微有价值的事,尽量不浪费您看文章的时间。

原定的计划

今天原定的任务是通过修改陈夏大佬的源文件,把两行的16X8字模的显示代码改为 四行8*5的代码。 要实现这个事儿,有两个主要内容。

1 找到显示效果比较好的8*5的显示字模

2 读懂SSD1306的点阵显示逻辑

第一条也简单,没啥可讲的,就是要花点时间。 要么找到现成的ASCII 8X15的字模文件,一般是bin格式的,要不找到适合显示的字体和字号,用字库生成工具来做这个事儿。

那剩下的时间,我们就一起分析下陈夏大佬写的这个SSD1306驱动吧,今天争取读懂先,那明天就有希望了。 讲真,改这驱动这种问题,对我这水平卡一个星期也不稀罕。 对于某些大佬比如 X旭,X姜,XXXdal大佬 这种级别的,可能就是喝瓶可乐的时间,这就是人与人的差距,这也是人家挣两万,我挣两千的原因,这个必须服气。

代码分析

我们的SSD1306驱动主要分为几层

最底层是I2C总线操作部分,通过不同的首字节内容区分发送的是命令,还是数据

-- 向OLED写入指令字节
function OLED_Write_Command(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x00, OLED_Byte})
end

-- 向OLED写入数据字节
function OLED_Write_Data(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x40, OLED_Byte})
end

初始化,和清屏命令,和显示字符一样,都是向寄存器写一些数据。 比较好动,我就不介绍了,我就直接跳到显示字符串这部分了。

OLED_ShowChar(x, y, Show_char,isChinese)

这是整个驱动程序的核心,作用是在指定位置,显示一个字符。 原来是兼容中文的,但是8X5肯定显示不清楚中文,我们就把相关代码移除,这种大褂改裤头的活儿,我最喜欢,精简化以后,代码就这样子,
function OLED_ShowChar(x, y, Show_char,isChinese)
local c, i = 0, 0

    if x > Max_Column - 1 then
        x = 0
        y = y + 2
    end -- 当列数超出范围,则另起2页
    local fontfile, error =nil,nil

    fontfile, error = io.open(fontfile_ascii, "r")
    c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量

    if fontfile == nil then
        --log.info("OLED_ShowChar", "fontfile-error:", error)
        return
    end
    fontfile:seek("set", 32*c)
    local F8X16 = fontfile:read(32)
    io.close(fontfile)
    --log.info("ssd1306","fontdata:",F8X16:toHex())
    if SIZE == 16 then -- 字符大小为[8*16]:一个字符分两页
        -- 画第一页
        OLED_Set_Pos(x, y)-- 设置画点起始处
        for i = 1, 8 do -- 循环8次(8列)
            --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2-1)):toHex())
            OLED_WR_Byte(string.byte(F8X16, i*2-1), OLED_DATA)-- 找到字模
        end
        -- 画第二页
        ---------------------------------------------------------
        OLED_Set_Pos(x, y + 1)-- 页数加1
        for i = 1, 8 do -- 循环8次
            --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2)):toHex())
            OLED_WR_Byte(string.byte(F8X16, i*2), OLED_DATA)-- 把第二页画完
        end       
    end
end

从代码中我们可以看出,默认的显示屏,是分为8列的,这是对于常规的128X64的显示屏,对于128X32,应该是4列,目前是每次每个字符分为两列来显示。 我们只需要做如下的修改

每次仅读取F5X8,五个字节的数据,并在一列中显示出来就行了。

代码今天是改不粗来了,明天再努力吧。 晚安,我内心不带愧疚的去睡觉啦!

菜鸟学写Cat1 Demo集(九)显示接口 -2020-08-31

八卦

当一名工程师不容易,讲真! 我身体不好,特别是心不好,累一点就撑不住。 但说实在的,我觉得996工作制很多男人是不怕的,炽血化春秋,这是他们的一种情怀。 问题是,谁没有个七情六欲,谁不是上有老下有小。 如果996在外,还不能把银子带回家,不被白眼是不可能的。

可是当下就是这么一个大环境,国产电子产品现在都是半卖半送价,成本摆在那了!为什么这么便宜? 因为不挣钱,甚至还有金主往里砸钱干!嘉立创的5元包邮时间可是不短了… 你说不挣钱,干嘛还要这样来,这肯定是为了挣长远的钱。 但现在这个年头,谁敢说十年以后啥样? 十年以后人在在不在地球都是个问题。 其实我这也是咸吃萝卜淡操心,自己的屁股还没擦干净,就替古人担忧。 不说了不说,还是说代码吧。

UI设计

我故意用了一个高大上的名字,彰显一下我过人的品味。 其实就是现实几行字,纯文本,图形都没有。因为我不会…

在第四期的,时候,我们的规划是这个样子的

菜鸟学写Cat1 Demo集(连载)

虽说现在项目的调调降低了,改DEMO集了。 我觉得顺手写一个显示界面也不吃亏! 不过即使是光显示文字对我来说也不轻松呀。 估计得弄个几期了。 菜鸟学习,急不得!

我买了一片最便宜的OLED显示屏,SSD1306驱动的,分辨率是128X32。 其实也许还有更便宜的,但I2C接口的正品,我知道的就属他最便宜了。 那就用他吧。 用I2C有几个原因 1)占用的硬件资源少,I2C总线挂了显示屏,理论上还可以继续挂接其他的传感器,也不影响使用。 比如再挂个温湿度传感器,加速度传感器啥的都可以。 当然速度肯定要打折扣。 另外I2C总线也比较娇气,电平适配不好,接触不良,总线容易卡死。

Air724的I2C总线电平是1.8V的,且不可调,因此如果要接3.3V的设备,是需要电平转换的,这个电路在官方开发板上有,我测了一下,新版的开发板,转换电路也是OK的。

菜鸟学写Cat1 Demo集(连载)

但是电话核心板这块是没有做电平转换的,也就是所高电平只有1.8V,为了满足高于2/3工作电压的要求,我把OLED显示屏的工作电压降到了2.5V,效果还可以。 当然这种方法并不推荐,设计一款好的电路的基本方法应该是严格按照设计来做,不应该随意妥协的设计,这种习惯很不好,我这样做是不对的。 如果您还要单独再接一个大彩屏,比如SST7735这种,那就最好接个LDO给OLED单独供电。 特别要注意的是,OLED对供电要求比较高,如果您自己画电路要小心,我画的这块板,用的是模块的VLCD,加了22uF的退耦电容,目前看稳定性挺凑合的。

驱动部分,我使用了陈夏同学无私分享的OLED代码,需要指出,他原来的代码支持更为常见的128X64分辨率的OLED,我把他改为了128X32分辨率,如果您需要128X64的代码,请参考陈夏的文章,原文链接如下

http://doc.openluat.com/article/701/0

不过这个代码还是有问题的,只能显示两行英文字母,且密度也比较低,我觉得起码要4行才行,8X6的字模应该可以。 但是目前的驱动是16X8的,所以得改。 中文就不支持了,显示ASCII编码就得了。 主要是为了省内存。 当然,我说的这些都放在以后去做,今天一如既往的偷懒,把代码扔出来,愉快的去睡觉了。

菜鸟学写Cat1 Demo集(连载)

代码

--- 模块功能:SSD 1306驱动芯片 I2C屏幕显示测试
-- @original author CX
-- @module midemo.oled
-- @license MIT
-- @release 2020.02.23

module(..., package.seeall)
require "bit"


local fontfile_ascii = "/lua/ASCII_lib.bin"--字库文件的地址,4G模块
local fontfile_gb2312 = "/lua/GB2312_lib.bin"--字库文件的地址,4G模块

local i2cid = 0
local i2cslaveaddr = 0x3c
-- 宏定义
local OLED_CMD = 0 -- 命令
local OLED_DATA = 1 -- 数据
local SIZE = 16 --显示字符的大小
local Max_Column = 128 --最大列数
local Max_Row = 64 --最大行数
local X_WIDTH = 128 --X轴的宽度
local Y_WIDTH = 64 --Y轴的宽度

-- 向OLED写入指令字节
function OLED_Write_Command(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x00, OLED_Byte})
end

-- 向OLED写入数据字节
function OLED_Write_Data(OLED_Byte)
    i2c.send(i2cid, i2cslaveaddr, {0x40, OLED_Byte})
end


-- 向OLED写入一字节数据/指令
function OLED_WR_Byte(OLED_Byte, OLED_Type)
    if OLED_Type == OLED_DATA then
        OLED_Write_Data(OLED_Byte)--写入数据
    else
        OLED_Write_Command(OLED_Byte)--写入指令
    end
end

-- 整屏写入某值
function OLED_Clear()
    local N_Page, N_row = 0, 0
    for N_Page = 1, 8 do
        OLED_WR_Byte(0xb0 + N_Page - 1, OLED_CMD)-- 从0~7页依次写入
        OLED_WR_Byte(0x00, OLED_CMD)-- 列低地址
        OLED_WR_Byte(0x10, OLED_CMD)-- 列高地址
        for N_row = 1, 128 do
            OLED_WR_Byte(0x00, OLED_DATA)
        end
    end
end

-- 设置数据写入的起始行、列
function OLED_Set_Pos(x, y)
    OLED_WR_Byte(0xb0 + y, OLED_CMD)-- 写入页地址
    OLED_WR_Byte(bit.band(x, 0x0f), OLED_CMD)-- 写入列的地址(低半字节)
    OLED_WR_Byte(bit.bor(bit.rshift(bit.band(x, 0xf0), 4), 0x10), OLED_CMD)-- 写入列的地址(高半字节)
end


--初始化OLED
-------------------------------------------------------------------------------
function OLED_Init()
    --i2c.set_id_dup(0)
    if i2c.setup(i2cid, i2c.SLOW) ~= i2c.SLOW then
        print("testI2c.init fail")
        return
    end

    --OLED_WR_Byte(0xAE, OLED_CMD)-- 关闭显示

    OLED_WR_Byte(0x00, OLED_CMD)-- 设置低列地址
    OLED_WR_Byte(0x10, OLED_CMD)-- 设置高列地址
    OLED_WR_Byte(0x40, OLED_CMD)-- 设置起始行地址
    OLED_WR_Byte(0xB0, OLED_CMD)-- 设置页地址

    OLED_WR_Byte(0x81, OLED_CMD)-- 对比度设置,可设置亮度
    OLED_WR_Byte(0xFF, OLED_CMD)-- 265

    OLED_WR_Byte(0xA1, OLED_CMD)-- 设置段(SEG)的起始映射地址
    OLED_WR_Byte(0xA6, OLED_CMD)-- 正常显示;0xa7逆显示

    OLED_WR_Byte(0xA8, OLED_CMD)-- 设置驱动路数(16~64)
    OLED_WR_Byte(0x1F, OLED_CMD)-- 64duty

    OLED_WR_Byte(0xC8, OLED_CMD)-- 重映射模式,COM[N-1]~COM0扫描

    OLED_WR_Byte(0xD3, OLED_CMD)-- 设置显示偏移
    OLED_WR_Byte(0x00, OLED_CMD)-- 无偏移

    OLED_WR_Byte(0xD5, OLED_CMD)-- 设置震荡器分频
    OLED_WR_Byte(0x80, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xD9, OLED_CMD)-- 设置 Pre-Charge Period
    OLED_WR_Byte(0xF1, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0xDA, OLED_CMD)-- 设置 com pin configuartion
    OLED_WR_Byte(0x02, OLED_CMD)-- 使用默认值

    OLED_WR_Byte(0xDB, OLED_CMD)-- 设置 Vcomh,可调节亮度(默认)
    OLED_WR_Byte(0x40, OLED_CMD)-- 使用官方推荐值

    OLED_WR_Byte(0x8D, OLED_CMD)-- 设置OLED电荷泵
    OLED_WR_Byte(0x14, OLED_CMD)-- 开显示

    OLED_WR_Byte(0xAF, OLED_CMD)-- 开启OLED面板显示

    --OLED_Clear()-- 清屏

    OLED_Set_Pos(0, 0)-- 设置数据写入的起始行、列
end

--在指定坐标处显示一个字符
-------------------------------------------------------------------------------
function OLED_ShowChar(x, y, Show_char,isChinese)
    local c, i = 0, 0

    if x > Max_Column - 1 then
        x = 0
        y = y + 2
    end -- 当列数超出范围,则另起2页
    local fontfile, error =nil,nil
    if (isChinese~=1) then
        fontfile, error = io.open(fontfile_ascii, "r")
        c = string.byte(Show_char) - 0x20 -- 获取字符的偏移量
    else
        log.info("OLED_ShowChar", "loadseq:", Show_char)
        fontfile, error = io.open(fontfile_gb2312, "r")
        c = Show_char -- 获取字符的偏移量
    end

    if fontfile == nil then
        log.info("OLED_ShowChar", "fontfile-error:", error)
        return
    end
    fontfile:seek("set", 32*c)
    local F8X16 = fontfile:read(32)
    io.close(fontfile)
    log.info("ssd1306","fontdata:",F8X16:toHex())
    if SIZE == 16 then -- 字符大小为[8*16]:一个字符分两页
        -- 画第一页
        OLED_Set_Pos(x, y)-- 设置画点起始处
        for i = 1, 8 do -- 循环8次(8列)
            if (isChinese~=1) then
                --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2-1)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2-1), OLED_DATA)-- 找到字模
            else
                --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2-1)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2-1), OLED_DATA)-- 找到字模
            end
        end
        -- 画第二页
        ---------------------------------------------------------
        OLED_Set_Pos(x, y + 1)-- 页数加1
        for i = 1, 8 do -- 循环8次
            if (isChinese~=1) then
                --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2), OLED_DATA)-- 把第二页画完
            else
                --log.info("ssd1306","left:",string.char(string.byte(F8X16, i*2)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2), OLED_DATA)-- 把第二页画完

            end
        end
        -- 中文画第三页
        ---------------------------------------------------------
        OLED_Set_Pos(x+8, y)-- 设置画点起始处
        for i = 1, 8 do -- 循环8次(8列)
            if (isChinese==1) then
                --log.info("ssd1306","right:",string.char(string.byte(F8X16, i*2+15)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2+15), OLED_DATA)-- 找到字模
            end
        end

        ---------------------------------------------------------
        OLED_Set_Pos(x+8, y + 1)-- 页数加1
        for i = 1, 8 do -- 循环8次
            if (isChinese==1) then
                --log.info("ssd1306","right:",string.char(string.byte(F8X16, i*2+16)):toHex())
                OLED_WR_Byte(string.byte(F8X16, i*2+16), OLED_DATA)-- 把第二页画完
            end
        end




    end
end



-- 在指定坐标起始处显示字符串
function OLED_ShowString(x, y, ShowStr)

    local len = #ShowStr
    local N_Char=1

    while N_Char <= len do
        local char_l=string.char(string.byte(ShowStr, N_Char))
        local char_r=""
        if (N_Char<len) then
            char_r=string.char(string.byte(ShowStr, N_Char+1))
        else
            char_r=" "
        end

        log.info("OLED_ShowChar",len,char_l:toHex().." "..char_r:toHex())

        if (char_l:byte()<0xA1) then
            OLED_ShowChar(x, y,char_l,nil)-- 显示一个英文字符
        else
            local seq=(char_l:byte()-0xA1)*94+char_r:byte()-0xA0-1
            log.info("OLED_ShowChar","Seq is ",seq)
            OLED_ShowChar(x, y,seq,1)-- 显示一个中文字符
            N_Char=N_Char+1
            x = x + 8 -- 列数加8 
        end
        x = x + 8 -- 列数加8,一个字符占8列
        if x >= 128 then
            x = 0
            y = y + 2
        end -- 当x>=128,另起一页

        N_Char=N_Char+1

    end
end

--控制SPI引脚的电压域
pmd.ldoset(8,pmd.LDO_VLCD)

菜鸟学写Cat1 Demo集(八)BIODEMO编码 2020-08-30

八卦

人都有犯迷糊的时候,但大多数人属于平时清醒,偶然迷糊。我正好反过来! 话说,当年和我一起工作过的同事,大多数都实现了小康生活,至少也没有债台高筑。分析一下我和他们唯一的区别,恐怕就是我不安分守己。 如果当初好好地在我过去呆过的任何一个单位好好干,不瞎折腾,不敢说财富自由,至少不会像现在这么痛苦。 所以,如果您现在有一份薪水还说的过去的工作,无论多苦多累,一定要忍住离职的冲动,和全公司的同仁同舟共济共渡难关。特别是小微企业,公司一共就那么几个人,谁能力如何,忠诚如何老板都清楚得很,人非草木皆有情,相信同渡难关定有后福。

你问我为啥不好好的打工,讲真不是不想,是因为欠债太多,想最后搏一搏,因为岁数毕竟不年轻了。 如果失败了,一定踏踏实实的找一个地方干,无论挣多挣少,养家糊口,老实还债,这是真心话。 如果您想创业,您一定要慎而再慎,我这就是前车之鉴,望君三思!

下面继续说代码的事儿。

功能

本DEMO包含两个指令

1) SETGPIO,pio,level

2) GETGPIO,pin

其中pin为0,31(不含8)之间的一个数字,代表了对应的GPIO口。 这里有两个坑要注意:

1) GPIO16是不能引发中断事件的

2) GPIO13上电的时候不可以为高电平,否则导致不开机

代码使用前,您需要先配置好BIOPins这个表,默认我们初始化了,0,1,2,3,9,10,11,12,13,18,19,20,21,这是根据Phoneboard核心板定义来的,您可以在淘宝店搜索【Air724电话核心板】买一块这个板,这个DEMO集后续也会按照这个板儿作为硬件平台来开发,花点钱您方便,我开心。

下面是测试结果:

菜鸟学写Cat1 Demo集(连载)

功能很简单

串口收到形如SETGPIO,pio,level的指令后,对应的pio引脚设置为level状态,比如 串口收到SETGPIO,13,1,则GPIO13引脚被设置为弱高电平

当任意双向引脚的电平发生变化时,本模块发送 GPIO_LEVEL_CHANGE 主题的消息,比如上条指令将发送系统消息 GPIO_LEVEL_CHANGE,13,1

发生电平变化的原因可以是内部指令造成的或者外部电路造成的,GPIO_LEVEL_CHANGE仅反映引脚的真实电平状态变化,当发生状态冲突,以实际外部电平为准

当串口收到SETGPIO指令时,串口将收到中文提示 GPIOxx当前电平为高/低的提示,如果电平发生真实的变化,串口还会收到GPIOXX上升沿/下降沿触发的提示

串口收到形如GETGPIO,pio的指令后,系统发送主题为GPIO_LEVEL 主题的消息,比如串口收到 GETGPIO,13 将发送系统消息GPIO_LEVEL,13,1

同时串口将收到中文提示 GPIOxx当前电平为高/低的

Air724电话核心板上为GPIO13板载了一个LED,您可以直接在板子上看到执行的效果
菜鸟学写Cat1 Demo集(连载)

代码

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭、陈夏等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)、LLCOM(Apache2)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:GPIO双向控制
-- @author miuser
-- @module midemo.bio
-- @license MIT
-- @copyright [email protected]
-- @release 2020-08-29
--------------------------------------------------------------------------
-- @使用方法
-- @首先配置BIOPins表,定义要被初始化为双向的引脚,初始化为双向引脚则不能另作他用
-- @串口收到形如SETGPIO,pio,level的指令后,对应的pio引脚设置为level状态,比如 串口收到SETGPIO,13,1,则GPIO13引脚被设置为弱高电平
-- @当任意双向引脚的电平发生变化时,本模块发送 GPIO_LEVEL_CHANGE 主题的消息,比如上条指令将发送系统消息 GPIO_LEVEL_CHANGE,13,1
-- @发生电平变化的原因可以是内部指令造成的或者外部电路造成的,GPIO_LEVEL_CHANGE仅反映引脚的真实电平状态变化,当发生状态冲突,以实际外部电平为准
-- @当串口收到SETGPIO指令时,串口将收到中文提示 GPIOxx当前电平为高/低的提示,如果电平发生真实的变化,串口还会收到GPIOXX上升沿/下降沿触发的提示
-- @串口收到形如GETGPIO,pio的指令后,系统发送主题为GPIO_LEVEL 主题的消息,比如串口收到 GETGPIO,13 将发送系统消息GPIO_LEVEL,13,1 
-- @同时串口将收到中文提示 GPIOxx当前电平为高/低的
-- @消息收发均采用utf8编码,与lua文件系统相同

require"pins"
require"utils"
require"pm"
require"common"

module(...,package.seeall)

--要映射为双向GPIO的引脚
 local BIOPins={3,1,0,2,12,11,9,10,19,18,21,20,13}
--上一次的电平状态表,以管脚号为索引
 local pinLastStage={}
--获取GPIO状态的函数表,以管脚号为索引
local getGpioFnc={}
--当收到GPIO输入测试的时候执行回调函数

--通过消息发送调试信息到串口模块
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("COM",s)
end
function gpioIntFnc(msg)
    local trigerPin=""
    local response=""
    --检测哪个IO口发生电平变化
    for i, v in ipairs(BIOPins) do
        if getGpioFnc[v]()~=pinLastStage[v] then
            trigerPin=v
            pinLastStage[v]=getGpioFnc[v]()
        end
    end
    if (trigerPin=="") then return end
    local level=getGpioFnc[trigerPin]()
    --if (level==0) then write("GPIO"..tostring(trigerPin).."当前电平为低".."\r\n") else write("GPIO"..tostring(trigerPin).."当前电平为高".."\r\n") end
    if msg==cpu.INT_GPIO_POSEDGE then
        response="GPIO"..tostring(trigerPin).."上升沿触发".."\r\n"
        write(response)
        --write("GPIO"..tostring(trigerPin).." rising".."\r\n")
        sys.publish("GPIO_LEVEL_CHANGE",trigerPin,1)
    --下降沿中断
    else
        response="GPIO"..tostring(trigerPin).."下降沿触发".."\r\n"
        write(response)
        --write("GPIO"..tostring(trigerPin).." falling".."\r\n")
        sys.publish("GPIO_LEVEL_CHANGE",trigerPin,0)
    end
end

write("初始化输入引脚")

pmd.ldoset(15,pmd.LDO_VLCD)
pmd.ldoset(2,pmd.LDO_VMMC)
for i=1,#BIOPins do
    pinLastStage[BIOPins[i]]=0
end
for i=1,#BIOPins do
    --设置中断函数和电平检测函数
    getGpioFnc[BIOPins[i]]=pins.setup(BIOPins[i],gpioIntFnc)
    --引脚均设为下拉
    pio.pin.setpull(pio.PULLDOWN,BIOPins[i])
    write(BIOPins[i].." ")
end
write("\r\n")

sys.subscribe("SETGPIO",function(...)

     io=tonumber(arg[1])
     level=tonumber(arg[2])
     write("设置弱上拉参数,".."端口为:"..tostring(io)..","..tostring(level).."\r\n")
    if (level~=0) then 
        pio.pin.setpull(pio.PULLUP,io)                 
     else
        pio.pin.setpull(pio.PULLDOWN,io)
    end
    level=getGpioFnc[io]()
    if (level==0) then write("GPIO"..tostring(io).."当前电平为低".."\r\n") else write("GPIO"..tostring(io).."当前电平为高".."\r\n") end    
end)

sys.subscribe("GETGPIO",function(...)

    io=tonumber(arg[1])
    level=getGpioFnc[io]()
    if (level==0) then write("GPIO"..tostring(io).."当前电平为低".."\r\n") else write("GPIO"..tostring(io).."当前电平为高".."\r\n") end
    sys.publish("GPIO_LEVEL",io,level)
end)

菜鸟学写Cat1 Demo集(七)GPIODEMO 2020-08-29

八卦

讲真,现在大家压力很大。 有社会的,有家庭的,有自身的,不一而论,所谓家家有本难念的经。 不过我还好,第一没有受大累,第二没有吃大苦。 没事就在空调房里吹吹水,也没挨饿-至少现在还没挨饿,这就得知足。 我赞同三体里的一句话 “生存从来不是理所应当的”,并深以为然。 所以埋头写代码啦,本事差,也不代表不能写。 我相信愚公移山,我相信水滴石穿,我相信精诚所至…得了,我再说估计砖头就飞上来了。

目标

今天我们搞GPIO,就是可以点灯的那种,不过不全是点灯的事儿。 您听我慢慢道来。
我在群里吹水的时候,经常听到有朋友有这样的发问: 我给GPIO XX 设置了高电平,怎么读出来就是低电平了呢。 我给出的解答,往往是,你设置的是输出模式,而你要读端口,这时是输入模式,刚才输出模式设置的高电平自然就消失了,然后吧啦吧啦一大堆,让用户去改他们的代码。

诚然,这是用户对单片机寄存器控制方法不理解造成的,但也不能全把问题推到用户身上。 因为按照变量的方式操作GPIO是一个很自然而然的习惯想法,是一个天然的诉求。 我们拿着 500MHz Cortex A5 核心的CPU,底层用着RTOS的核心,用着最潮的LUA脚本,难道就不能满足客户的要求么? 我想试试,当然是有前提的。

硬件资源

1)Air724 GPIO口具有上拉和下拉的功能,可以在输入模式情况下,给对应的模块引脚输出高电平和低电平。

2)上拉和下拉电流都非常弱,uA级别,如果作为输出使用必须加放大,如果是继电器一类的负载,需要采用场效应管或者达林顿管驱动。

结论

配合恰当的硬件设计,客人的使用习惯可以得到满足。 但性能尚不知晓,后面的日记中进行实测

代码

这次沿袭上一次的套路又要偷懒了,先把过去写的代码贴上来,改了版权声明,明天再做进一步的修改,融入到demo集中来,这样又愉快的混过一天。

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:GPIO双向控制
-- @author miuser
-- @module midemo.bio
-- @license MIT
-- @copyright [email protected]
-- @release 2020-08-29
module(...,package.seeall)

require"pins"
require"utils"
require"pm"
require"common"

--克隆表格
function clone(org)
    local function copy(org, res)
        for k,v in pairs(org) do
            if type(v) ~= "table" then
                res[k] = v;
            else
                res[k] = {};
                copy(v, res[k])
            end
        end
    end
    local res = {}
    copy(org, res)
    return res
end

--要测试的功能管脚 共计 15个
local pinToTest={3,1,0,2,12,11,9,10,19,18,21,20,13}
--上一次的电平状态表,以管脚号为索引
local pinLastStage={}
--获取GPIO状态的函数表,以管脚号为索引
local getGpioFnc={}
--尚未测试的引脚
local untested=clone(pinToTest)
--测试完成的标记
local done=0
--当收到GPIO输入测试的时候执行回调函数
sys.subscribe("GPIO_INPUT_TEST", function()
    write("初始化输入引脚")
    write("请依次按下所有GPIO对应的按钮")
    pmd.ldoset(15,pmd.LDO_VLCD)
    pmd.ldoset(2,pmd.LDO_VMMC)
    for i=1,#pinToTest do
        pinLastStage[pinToTest[i]]=0
    end
    for i=1,#pinToTest do
        --设置中断函数和电平检测函数
        getGpioFnc[pinToTest[i]]=pins.setup(pinToTest[i],gpioIntFnc)
        --引脚均设为下拉
        pio.pin.setpull(pio.PULLDOWN,pinToTest[i])
    end

end)

sys.subscribe("SETPULLUP",function(port)
    write("设置弱上拉".."端口为:"..tostring(port))
    pio.pin.setpull(pio.PULLUP,port)
end)

sys.subscribe("SETPULLDOWN",function(port)
    write("设置弱下拉".."端口为:"..tostring(port))
    pio.pin.setpull(pio.PULLDOWN,port)
end)

sys.subscribe("SETNOPULL",function(port)
    write("设置无上拉".."端口为:"..tostring(port))
    pio.pin.setpull(pio.NOPULL,port)
end)


function gpioIntFnc(msg)
    --输入测试完成的标识,为1则不再响应中断
    if (done==1) then return end

    local trigerPin=""
    local response=""
    --检测哪个IO口发生电平变化
    for i, v in ipairs(pinToTest) do
        if getGpioFnc[v]()~=pinLastStage[v] then
            trigerPin=v
            pinLastStage[v]=getGpioFnc[v]()
        end

    end
    if (trigerPin=="") then return end
    local level=getGpioFnc[trigerPin]()
    if (level==0) then write("GPIO"..tostring(trigerPin).."当前电平为低") else write("GPIO"..tostring(trigerPin).."当前电平为高") end
    if msg==cpu.INT_GPIO_POSEDGE then
        response="GPIO"..tostring(trigerPin).."上升沿触发"
        write(response)
    --下降沿中断
    else
        response="GPIO"..tostring(trigerPin).."下降沿触发"
        write(response)
    end

    --标记测试完的引脚
    for i, v in ipairs(untested) do
        if (trigerPin==v) then
            table.remove(untested,i)
            pio.pin.setpull(pio.PULLUP,trigerPin)
        end
    end
    write("剩余测试按键数为"..#untested)

    --如果未测引脚为0,则测试完成
    if #untested==0 then
        rtos.sleep(1000)
        for i, v in ipairs(pinToTest) do
            pio.pin.setpull(pio.PULLDOWN,v)
        end
        write("输入测试完成")
        sys.publish("GPIO_INPUT_TEST_DONE")
        done=1
    end
end

--通过消息发送调试信息到串口模块
function write(s)
    --log.info("testUartTask.write",s)
    sys.publish("UARTOUT",s)
end


菜鸟学写Cat1 Demo集(六)测试接口编码 2020-08-28

八卦

昨天晚上卡着11:59分发了文,结果就出事故了… 版权这一段,居然忘记了加上MIT版权说明,结果大佬立即善意的提醒了出来,所以今天亡羊补牢,先把版权声明写出来,希望您也能帮我一起看看,还有没有什么没想到的。

-- 版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 

今天的任务比较轻松,就是把昨天没写完的测试接口写完。 跟您说实话,我曾经也算是个技术工程师,但是08年之后,由于工作需要更多的是从事供应链管理,大客户管理等等相关的工作,技术几乎荒废。所以我谈到的我过去如何如何其实是指10+年前以前的段子。

两年前一个偶然的机会接触到合宙开源社区,一下子就被吸引住了。 这个社区的大佬们与别的不同,真的都是平地抠饼,对面拿贼靠技术吃饭的师傅们,所以牙一咬就留下磕头拜师开始了学习之旅。 这两年不敢说学到了多少,不过合宙的模块基本上能玩动了。 对于一个前80后,这个成绩我已经非常地开心了。 而现在所做的,就是把我这两年所学到的,写出来,方便后来的您。 如果顺便还能解决下自己的饭票问题,那就更理想了,哈哈哈。

编码

今天要写的主要是测试编码以及测试测试编码的代码(这不是绕口令…)

1 com.lua

串口命令模块:通过串口发送demo测试命令,可以映射系统消息到1至多个串口

2 main.lua

主函数,入口

3 test.lua

打环测试“测试代码”

过去晨旭大佬教过我几下git,但是我也一直搞不太清,就拿这个项目试试吧。 学多少用多少。 项目地址为:

https://gitee.com/miuser00

记得给我点赞哦,你点了,我多吃一个卤蛋。

运行效果

1 对于任何串口收到的指令,则尾部加上OK回送。
2 对于特定的消息,比如“TEST” 这是由我们测试的测试脚本决定的,处理一下再打环发回来,表示消息被成功执行并处理了

菜鸟学写Cat1 Demo集(连载)

测试接口代码 (com.lua)

--------------------------------------------------------------------------------------------------------------------------------------- 
--版权声明:本demo集源于上海合宙官方技术团队的DEMO(MIT),并参考了合宙开源社区的众多大佬无私分享的代码,包括但不限于 稀饭放姜、Wendal、晨旭等
-- 目前参考到的开源项目有: iRTU(MIT)、LuatOS(MIT)
-- 欲获取更多luat 代码请访问 doc.openluat.com
-- 如果您觉得本demo集包含了未经由您授权的代码,请联系 [email protected] 
---------------------------------------------------------------------------------------------------------------------------------------

-- @模块功能:串口命令模块
-- @author miuser
-- @module midemo.com
-- @license MIT
-- @copyright [email protected]
-- @release 2020-08-28
--------------------------------------------------------------------------
-- @使用方法
-- @外部串口发送指令形如 COMMAND,PARA1,PARA2,PARA3.... 参数中不可以有逗号
-- @串口收到指令后通过sys.publish(...)的形式转化为系统消息,发布出去。
-- @当sys.subscribe(...) 接收到任何主题为“COM”,则转发到串口
-- @消息收发均采用utf8编码,与lua文件系统相同
-------------------------------------------- 配置串口 ---------------------
require "common"
module(...,package.seeall)

-- 串口ID 对应硬件串口 1,2,3 端口号为 123 和USB虚拟串口,端口号为129
--暂时先把所有的串口都写上,方便测试
local UART_IDs={1,2,3,129}  
-- 串口ID,串口读缓冲区
local sendQueue= {}
-- 串口超时,串口准备好后发布的消息
-- 这个参数要根据波特率调整,波特率如果比较低,相应的timeout要延长,单位是ms
-- 一般来说115200bps建议用25, 9600bps建议调到100
local uartimeout= 25
--保持系统处于唤醒状态,不会休眠
pm.wake("com")
-- 初始化所有被指派为控制台的串口
for i=1,#UART_IDs do
    uart.setup(UART_IDs[i], 115200, 8, uart.PAR_NONE, uart.STOP_1, nil, 1)

    uart.on(UART_IDs[i], "receive", function(uid)
        table.insert(sendQueue, uart.read(uid, 8192))
        sys.timerStart(sys.publish, uartimeout, "COMRSV")
    end)
end

-- 1 向串口发送收到的字符串加上->OK,并回送到串口
-- 2 将串口收到的消息转发成luatask系统消息
sys.subscribe("COMRSV", function()
    local str = table.concat(sendQueue)
    -- 串口的数据读完后清空缓冲区
    local splitlist = {}
    string.gsub(str, '[^,]+', function(w) table.insert(splitlist, w) end)
    local count=table.getn(splitlist)
    --sys.publish("UARTIN",str)
    for i=1,#splitlist do 
        splitlist[i]=common.gb2312ToUtf8(splitlist[i])
    end 
    sys.publish(unpack(splitlist))
    sendQueue = {}
    log.info("uart read:", str)
    write(str.."->OK".."\n\r")
end)

-- 向所有串口发送字符串
function write(str)
    for i=1,#UART_IDs do
        uart.write(UART_IDs[i], str)
    end
end

--从系统消息接收主题为“COM”的消息,并转发到串口
local function uartrsv(msg)
    for i=1,#UART_IDs do
        uart.write(UART_IDs[i], common.utf8ToGb2312(msg))
    end
end
sys.subscribe("COM", uartrsv)

测试测试接口的代码 (test.lua)

module(...,package.seeall)

function callback(...)
    sys.publish("COM","测试字符为:")
    for i=1,#arg do 
        sys.publish("COM",arg[i])
    end
end
sys.subscribe("TEST", callback)

#入口函数(main.lua)

PROJECT = "Mi_Demo"
VERSION = "0.0.1"
AUTHOR = "Miuser"

--加载日志功能模块,并且设置日志输出等级
--如果关闭调用log模块接口输出的日志,等级设置为log.LOG_SILENT即可
require "log"
LOG_LEVEL = log.LOGLEVEL_TRACE
--[[
如果使用UART输出日志,打开这行注释的代码"--log.openTrace(true,1,115200)"即可,根据自己的需求修改此接口的参数
如果要彻底关闭脚本中的输出日志(包括调用log模块接口和Lua标准print接口输出的日志),执行log.openTrace(false,第二个参数跟调用openTrace接口打开日志的第二个参数相同),例如:
1、没有调用过sys.opntrace配置日志输出端口或者最后一次是调用log.openTrace(true,nil,921600)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false)即可
2、最后一次是调用log.openTrace(true,1,115200)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false,1)即可
]]
--log.openTrace(true,1,115200)

require "sys"
require "net"
--每1分钟查询一次GSM信号强度
--每1分钟查询一次基站信息
net.startQueryAll(60000, 60000)

--加载控制台调试功能模块(此处代码配置的是uart2,波特率115200)
--此功能模块不是必须的,根据项目需求决定是否加载
--使用时注意:控制台使用的uart不要和其他功能使用的uart冲突
--使用说明参考demo/console下的《console功能使用说明.docx》
--require "console"
--console.setup(2, 115200)

--加载网络指示灯和LTE指示灯功能模块
--根据自己的项目需求和硬件配置决定:1、是否加载此功能模块;2、配置指示灯引脚
--合宙官方出售的Air720U开发板上的网络指示灯引脚为pio.P0_1,LTE指示灯引脚为pio.P0_4
-- require "netLed"
-- pmd.ldoset(2,pmd.LDO_VLCD)
-- netLed.setup(true,pio.P0_1,pio.P0_4)
--网络指示灯功能模块中,默认配置了各种工作状态下指示灯的闪烁规律,参考netLed.lua中ledBlinkTime配置的默认值
--如果默认值满足不了需求,此处调用netLed.updateBlinkTime去配置闪烁时长
--LTE指示灯功能模块中,配置的是注册上4G网络,灯就常亮,其余任何状态灯都会熄灭

--加载错误日志管理功能模块【强烈建议打开此功能】
--如下2行代码,只是简单的演示如何使用errDump功能,详情参考errDump的api
require "errDump"
errDump.request("udp://ota.airm2m.com:9072")


--加载远程升级功能模块【强烈建议打开此功能,如果使用了阿里云的OTA功能,可以不打开此功能】
--如下3行代码,只是简单的演示如何使用update功能,详情参考update的api以及demo/update
--PRODUCT_KEY = "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K"
--require "update"
--update.request()


--加载调试模块

rtos.sleep(5000)

require "com"
require "test"

--errDump.appendErr("Error dump test")

--启动系统框架
sys.init(0, 0)
sys.run()

菜鸟学写Cat1 Demo集(五)测试接口编写 2020-08-27

八卦

我以前写代码的时候,有两种主要的方式

1) 随心随性的写,这样的代码,后期往往是连我都不认识了,可维护性几乎为零,写过的代码,看了想吐,绝不愿意去修bug

2) 花大力气,写框架,然后一部分一部分的完成,通常情况下写了30%左右就没动力往下弄了。

这次我希望能把这个项目做完所以打算尝试一种新的方法。 边写框架,边写代码。 测试、文档、核心代码、都放在同等的重要,同步完成。 具体下来有几个原则:

1) 边写核心代码,边测试,务必把所有的基本测试都跑清楚了,运行稳定了,文档写全了,再写新代码。

2) 随写随公布代码,方便大家使用,也督促我能提高兴致。

3) 稳定优先,不追求新功能。 锁定硬件版本、固件版本开发demo,保证所有demo的与固件同步可用。

Demo集文件结构

1 com.lua

串口命令模块:通过串口发送demo测试命令,可以映射系统消息到1至多个串口

2 bs.lua

核心板适配模块:我们希望这个demo集的核心模块部分支持合宙所有的cat1模块,核心板。 所以板子的适配部分单独出来定义。

x XXX.lua

除了以上两个文件,其余的都是核心demo文件,每个模块严格的只完成一个功能,多个功能则分别编写。 尽量减小单模块的代码量,便于阅读。

主板模块的适配文件,

测试接口编写

我在做复古电话的时候,很多想法其实已经有了,所以当时写的一些代码,拿过来修修改改,基本就可以用了。 今天先把定义写完,明天再改,这样就又混过一天。

--- 模块功能:串口命令模块
-- @author miuser
-- @module midemo.com
-- @license MIT
-- @copyright miuser
-- @release 2020-08-27

-- 使用方法
-- 外部串口发送指令形如 COMMAND,PARA1,PARA2,PARA3.... 参数中不可以有逗号
-- 串口收到指令后通过sys.publish(...)的形式转化为系统消息,发布出去。
-- 当sys.subscribe(...) 接收到任何主题为“COM”,则转发到串口

-------------------------------------------- 配置串口 --------------------------------------------
-- 串口ID,串口读缓冲区
local UART_ID, sendQueue= 129, {}
-- 串口超时,串口准备好后发布的消息
local uartimeout= 25
--保持系统处于唤醒状态,不会休眠
pm.wake("mcuart")
uart.setup(UART_ID, 115200, 8, uart.PAR_NONE, uart.STOP_1, nil, 1)

uart.on(UART_ID, "receive", function(uid)
    table.insert(sendQueue, uart.read(uid, 8192))
    sys.timerStart(sys.publish, uartimeout, "UART_RECV_ID")
end)



local function write(str)
    --软串口
    uart.write(UART_ID, str)
end

-- 向串口发送收到的字符串
sys.subscribe("UART_RECV_ID", function()
    local str = table.concat(sendQueue)
    -- 串口的数据读完后清空缓冲区
    local splitlist = {}
    string.gsub(str, '[^,]+', function(w) table.insert(splitlist, w) end)
    local count=table.getn(splitlist)
    sys.publish("UARTIN",str)
    if (count==1) then
        log.info("sys.publish",splitlist[1])
        sys.publish(splitlist[1])
    elseif count==2 then
        sys.publish(splitlist[1],splitlist[2])
    elseif count==3 then
        sys.publish(splitlist[1],splitlist[2],splitlist[3])
    end

    sendQueue = {}
    log.info("uart read length:", #str,"Port"..UART_ID)
    write(str.."->OK".."\n\r")

end)

local function uartrsv(msg)
    uart.write(UART_ID,common.utf8ToGb2312(msg).."\n\r")
end
sys.subscribe("COM", uartrsv)

今天先到这里

菜鸟学写Cat1 Demo集(四)搭架子 2020-08-26

搭架子

今天项目基本上没啥进展,主要是在做思维体操–其实就是睡大觉。 考虑如何写结构才能让DEMO使用起来最顺手。 结果只是又画出了一个草图,先沉淀下–其实就是偷一天懒..
菜鸟学写Cat1 Demo集(连载)

您如果有任何意见建议,欢迎在doc.openluat.com留言,或者QQ我,号码是64034373 非常欢迎您的意见,建议,我感激涕零。

菜鸟学写Cat1 Demo集(三)先做一个原型 2020-08-25

决定放水

又经过了一天的发酵,听取了顶层大佬的建议,我觉得开发指令集这个事儿难度太高了,我现有能力还是不太够。 如果这么搞,最终极有可能无功而返,打脸收场。 因此根据自身现阶段的能力,我把给自己出的题目改了一下,改为做一个极简的Demo集,这样子就和我现阶段的状态匹配了。

说过后我的压力小多了…希望没有太让您失望,我也会尽力而为把Demo往好了做,方便模块使用,谢谢,谢谢。

客人对Demo的诉求

下面我说一下这个Demo集的设计套路。 首先还是让我站在两年前作为一名合宙的客人的位置上来想。

1) 我对于模块的功能的了解来源于Demo,因为拿Demo可以直接来用,验证模块的相应功能,让我直观的对模块性能有所体验

2) 不希望太复杂,最好是一目了然,看清构造和功能方便我吸收成为自己的东西

3) 每个Demo之间没有干涉,把demo的模块攒在一起就可以完成系统工作

4) 最核心的一点,必须要可靠,最好是能应用于实战级别的Demo,而不是仅仅能展示一会就挂了

5) 说明模块与外部设备的连接方式,用不着让我再去倒电路图查如何连线,节约宝贵时间

6) 方便测试

开发套路

针对以上这些,我打算尝试这样来开发

1) 按照功能、设备、尽量把Demo拆细,每个模块包含尽量少的内容,方便您的阅读

2) 每个模块留有测试接口,可以通过Luatask机制发消息进行调试

3) 模块之间留有一致的通讯接口,方便拼接

4) 去除模块之间的强耦合,方便裁剪

5) 编写统一的测试说明文档,方便对Demo功能的阅读理解

现有资源

合宙官方其实有非常丰富的自带Demo,就在LuatoolsV2的resource目录,我的一切的开发也都肯定是要建立在这些demo的基础上。我打算要做的事是把这些demo简单化,通俗化。 毕竟高大上永远不属于我! 普通才是我的真爱。 我的计划是先跟合宙的FAE专家以及研发大佬弄清楚这些Demo的用法,然后再把功能简单化,傻瓜化。 太复杂的我学不会的就直接bypass了。 所以如果您是高手,请务必绕行,去看官方的demo,不要看我这个,真心没啥用,功能肯定没有官方的demo全。

Demo集的原型

我这人比较奇葩,做什么事都得先摆个架子,左顾右盼,望三望才肯开工… 其实主要是不知道如何下手,所以如同那个怀旧复古电话一样,我会先做一个原型,把基本的套路弄出来,后面再顺着这个来。 这次也不例外Demo集也得有个原型。

我们一起来看一下,首先是说明文档:

Air724 GPIO输入测试
功能
1 通过UART虚拟AT串口(设备管理器里可以看到端口号)发送控制命令,对指定GPIO口发送输入状态查询指令
2 如果对应的GPIO口,电平发生状态则打印当前状态
测试演示
[2020-04-22 22:05:35.9710] ← GPIO,13,UP
[2020-04-22 22:05:36.2054] → Command is GPIO,13,UP
[2020-04-22 22:05:50.3817] → 当前测量引脚输入电平为高
GPIO13上升沿触发
[2020-04-22 22:05:50.6629] → 当前测量引脚输入电平为低
GPIO13下降沿触发
[2020-04-22 22:05:51.0222] → 当前测量引脚输入电平为高
GPIO13上升沿触发
[2020-04-22 22:05:51.3191] → 当前测量引脚输入电平为低
GPIO13下降沿触发

菜鸟学写Cat1 Demo集(连载)

菜鸟学写Cat1 Demo集(连载)

下面是源码:

--- 模块功能:GPIO功能测试.
-- @author openLuat
-- @module gpio.testGpioSingle
-- @license MIT
-- @copyright miuser
-- @release 2020-08-25

module(...,package.seeall)

require"pins"
require"utils"
require"pm"
require"common"

local PIO=0

function gpioIntFnc(msg)
    local response=""
    log.info("testGpioSingle.gpioIntFnc",msg,getGpioFnc())
    --上升沿中断
    if getGpioFnc()==1 then response="当前测量引脚输入电平为高" else response="当前测量引脚输入电平为低" end
    log.info("testGpioSingle.gpioIntFnc",response)
    write(common.utf8ToGb2312(response))

    if msg==cpu.INT_GPIO_POSEDGE then
        response="GPIO"..tostring(PIO).."上升沿触发"
        log.info("testGpioSingle.gpioIntFnc",response)
        write(common.utf8ToGb2312(response))

    --下降沿中断
    else
        response="GPIO"..tostring(PIO).."下降沿触发"
        log.info("testGpioSingle.gpioIntFnc",response)
        write(common.utf8ToGb2312(response))
    end
end


PIO=13

getGpioFnc = pins.setup(PIO,gpioIntFnc)
--pins.close(13)
log.info("info","GPIO"..tostring(PIO).." is initalized")

pmd.ldoset(15,pmd.LDO_VLCD)
pmd.ldoset(15,pmd.LDO_VMMC)


--获取要测试的GPIO端口号


--串口ID,2对应uart2
--如果要修改为uart1,把UART_ID赋值为2即可
local UART_ID = 129

local setGpio1Fnc = pins.setup(1,1)

local function taskRead()
    local cacheData,frameCnt = "",0
    while true do
        local s = uart.read(UART_ID,"*l")
        if s == "" then            
            if not sys.waitUntil("UART_RECEIVE",100) then
                --uart接收数据,如果100毫秒没有收到数据,则打印出来所有已收到的数据,清空数据缓冲区,等待下次数据接收
                --注意:
                --串口帧没有定义结构,仅靠软件延时,无法保证帧的完整性,如果对帧接收的完整性有严格要求,必须自定义帧结构(参考testUart.lua)
                --因为在整个GSM模块软件系统中,软件定时器的精确性无法保证,例如本demo配置的是100毫秒,在系统繁忙时,实际延时可能远远超过100毫秒,达到200毫秒、300毫秒、400毫秒等
                --设置的延时时间越短,误差越大
                if cacheData:len()>0 then
                    log.info("testUartTask.taskRead","100ms no data, received length",cacheData:len())
                    --数据太多,如果全部打印,可能会引起内存不足的问题,所以此处仅打印前1024字节
                    log.info("testUartTask.taskRead","received data",cacheData:sub(1,1024))
                    write("Command is "..cacheData)

                    frameCnt = frameCnt+1
                    --write("received "..frameCnt.." frame")


                    if (string.sub(cacheData,1,4)=="GPIO") then
                        --分割命令
                        local splitlist = {}
                        string.gsub(cacheData, '[^,]+', function(w) table.insert(splitlist, w) end)
                        --lua下标从1开始
                        local s_GPIO=splitlist[2]
                        local i_GPIO=tonumber(s_GPIO)
                        local s_STATUS=splitlist[3]
                        --关闭原来的IO
                        pins.close(PIO)
                        PIO=i_GPIO
                        getGpioFnc = pins.setup(PIO,gpioIntFnc)

                        if s_STATUS=="UP" then pio.pin.setpull(pio.PULLUP,PIO) end
                        if s_STATUS=="DOWN" then pio.pin.setpull(pio.PULLDOWN,PIO) end
                        if s_STATUS=="NO" then pio.pin.setpull(pio.NOPULL,PIO) end


                    end
                    cacheData = ""
                end
            end
        else
            cacheData = cacheData..s
        end
    end
end

--[[
函数名:write
功能  :通过串口发送数据
参数  :
        s:要发送的数据
返回值:无
]]
function write(s)
    log.info("testUartTask.write",s)
    uart.write(UART_ID,s.."\r\n")
end

local function writeOk()
    log.info("testUartTask.writeOk")
end


--保持系统处于唤醒状态,此处只是为了测试需要,所以此模块没有地方调用pm.sleep("testUartTask")休眠,不会进入低功耗休眠状态
--在开发“要求功耗低”的项目时,一定要想办法保证pm.wake("testUartTask")后,在不需要串口时调用pm.sleep("testUartTask")
pm.wake("testUartTask")
--注册串口的数据发送通知函数
uart.on(UART_ID,"sent",writeOk)
uart.on(UART_ID,"receive",function() sys.publish("UART_RECEIVE") end)
--配置并且打开串口
uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1,0,1)
--如果需要打开“串口发送数据完成后,通过异步消息通知”的功能,则使用下面的这行setup,注释掉上面的一行setup
--uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1,nil,1)
--启动串口数据接收任务
sys.taskInit(taskRead)

pmd.ldoset(15,pmd.LDO_VLCD)
pmd.ldoset(15,pmd.LDO_VMMC)

由于是小样,代码肯定一团糟,因为我只是把官方demo里的代码简单的拼接了一下,而且也没有分文件编写,所以比较乱,但是效果还是达到了。这也就达到原型的目的了。

今天就酱紫了,算是草草交差了,感谢您的阅读,明天再见。

菜鸟学写Cat1简单AT指令集(二)草图 2020-08-24

#草图

对于物联网这个行业,最高端玩家往往是直接拿原厂芯片方案,量大到一定程度,根本不愁开发,因为原厂往往都有比较成熟的方案,没有现成的也可以代开发,还不收费。 所以愿意用模块的往往是中小型客户,他们要做的是某一细分领域的终端用户产品。 买模块的主要目的是简化开发流程,缩短开发周期,尽快把产品推向市场,抢占先机。 下面我试着画一个最基本用户的草图。

菜鸟学写Cat1 Demo集(连载)

1)期望即学即用,快速上手求,入门时间1-2星期既可以开始简单的开发。

2)用最简单直接的方法建立PC,手机设备与硬件的连接,可以双向收发指令,稳定可靠。

3)通过指令可以控制GPIO和常见的总线外部设备,达到控制,显示,发声,数据采集 等功能。

#用户分析

对于模块而言需要处理的实际上硬件资源分配,总线管理,通讯逻辑实现这些非常底层的需求。 先举一个最简单的本地控制SHT20温湿度传感器连接Air724实现温湿度采集这个功能来举例,模块采用Luat方式编程处理的是这样的指令。

if not i2c_open(id) then return end
i2c.send(id, addr, 0xE3)
tmp = i2c.recv(id, addr, 2)
log.info("SHT读取到的温度寄存器24位值:", tmp:toHex())
i2c.send(id, addr, 0xE5)
hum = i2c.recv(id, addr, 2)
log.info("SHT读取到的湿度寄存器24位值:", hum:toHex())
i2c.close(id)

但大多数人更喜闻乐见的可能是发送一个指令,直接读取温湿度

->GET SHT20TEMP
<-Tempture:23 Humidity:40%

再比如连接阿里云平台

cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
cleansession, qos = tonumber(cleansession) or 0, tonumber(qos) or 0
local data = "clientId" .. sim.getIccid() .. "deviceName" .. deviceName .. "productKey" .. ProductKey
local usr = deviceName .. "&" .. ProductKey
local pwd = crypto.hmac_sha1(data, #data, deviceSecret, #deviceSecret)
local clientID = sim.getIccid() .. "|securemode=3,signmethod=hmacsha1|"
local addr = ProductKey .. ".iot-as-mqtt." .. RegionId .. ".aliyuncs.com"
local port = 1883
if type(sub) ~= "string" or sub == "" then
    sub = ver:lower() == "basic" and "/" .. ProductKey .. "/" .. deviceName .. "/get" or "/" .. ProductKey .. "/" .. deviceName .. "/user/get"
else
    sub = listTopic(sub, "addImei", ProductKey, deviceName)
    local topics = {}
    for i = 1, #sub do
        topics[sub[i]] = tonumber(sub[i + 1]) or qos
    end
    sub = topics
end
if type(pub) ~= "string" or pub == "" then
    pub = ver:lower() == "basic" and "/" .. ProductKey .. "/" .. deviceName .. "/update" or "/" .. ProductKey .. "/" .. deviceName .. "/user/update"
else
    pub = listTopic(pub, "addImei", ProductKey, deviceName)
end

客人可能希望的是输入三元组信息,提供订阅和发布的主题然后直接建立透传:

->CONNECT ALIYUN ProductKey:XXXXX AccessKey:XXXXXX Secrect:XXXXXX
<-AUTHOR OK, CONNECTED
->SUBSCRIBE /Mytopic
<-OK
PUBLISH /Mytopic
<-OK

对于显示屏和喇叭,能提供菜单化的交互环境固然不错,但是对于快速应用,能输出简单的文字,显示出当前的网络状态,信号质量,云平台状态这些对于普通用户其实也是很实用的功能。

菜鸟学写Cat1 Demo集(连载)

如果我们把合宙的模块比喻成一台V8发动机,虽然用户知道他可能能精心装配出一台Ferrari,而用户实际上只需要的是拧上几个螺丝,接上轮子可以带动一台翻斗车。 因为模块足够的便宜、应用场景足够广阔且粗放!

菜鸟学写Cat1 Demo集(连载)

思路汇总

如果以上对客户的假设是真实正确的,那么以下是我们要做的工作:

1)将现有纷繁的复用硬件引脚进行整理归纳,锁定功能配置,简化功能操作。 硬件与各类外设的搭配进行固化,以方便软件层进行归一化处理

菜鸟学写Cat1 Demo集(连载)

2)利用Luat框架,将最主要的功能集中编写,并提供一个更为精简的AT指令集,完成设备级别的控制操作,如

菜鸟学写Cat1 Demo集(连载)

信息输出类: 各种显示屏,如ST7735,IL9341,SSD1306等 喇叭声音输出

用户交互:按钮控制

简单输入输出:开关量输入输出,ADC输入等

总线设备控制:温湿度传感器,位置加速度传感器,外挂2.4G及以太网通讯设备,IC卡阅读器 等

3)简化云平台通讯流程

菜鸟学写Cat1 Demo集(连载)

建立模块私有通讯通道连接到开源服务端,可以通过模块自身二维码直接控制模块

提供简单的连接命令,通过简短的一两条指令和第三方云服务平台连接

#八卦

这个项目刚刚开始就有热心的群友提出了自己的建议,比如有人提到,这个项目很多功能其实和iRTU固件是重合的。但其实还是有一些差别的,iRTU面向的是主要透传通讯领域,功能相对明确,而这个项目面而将涵盖模块硬件控制等更众多的功能。 另外在开发深度定位上也不同,iRTU固件要做的事是把透传这件事做专精,而MinimusAT项目要做的是简化模块全功能入门级应用,并不打算实现太多的专精功能。

但是这个群友的观点启发了我,既然iRTU固件很优秀,很稳定,为什么不能直接拿来用呢?因此后续我将iRTU固件作为透传通讯模块尝试组合在MinimusAT项目里,正好过去没有足够的精力研究iRTU固件,这次可以好好深入学习下稀饭老师的代码了,这主意真是太棒了! 只是希望我的智商和能力不欠费就好!

菜鸟学写Cat1简单AT指令集(一)引子 2020-08-23

#引子:

本着“为了做而做”的态度,我打算开发一套模块指令集,为了与别人不一样,我们先分析一下现有的物联网模块的开发套路。

1) 标准AT开发

菜鸟学写Cat1 Demo集(连载)

优点: 套路清晰,普通用户认知度高

缺点: 基于状态机的指令集,用起来让人头大,如果逻辑设计不好,可靠性不佳,稳定性存疑。

2) Luat开发

菜鸟学写Cat1 Demo集(连载)

优点:功能全,性能优

缺点:需要会lua语言,熟悉luat架构,技术门槛高,学习周期长。

3) iRTU(SmartDTU)开发

菜鸟学写Cat1 Demo集(连载)

优点:免开发,菜单化配置

缺点:围绕透传单一功能展开,功能相对较少,文档薄弱

4) CSDK开发

菜鸟学写Cat1 Demo集(连载)

优点:功能最全,开发自由度大

缺点:入门难,上手难,精通更难,没有官方支持。

综合以上,可以看到合宙生态的开发工具一点都不单薄,是不是我就没有事可做了呢?显然不是!

##需求分析:

回想我两年前初次接触物联网行业,我对模块的基本诉求就是给他发几个指令,然后连上服务器通讯。 我希望的模块指令集是这个样子的。

1) 模块启动后,通知我一切准备就绪

2) 我告诉模块去连接哪的服务器,给他鉴权信息

3) 他帮我联网,然后维护连接。

这样,数据你来我往,畅通透传,5分钟工作愉快的完成了,剩下的时间可以愉快的玩耍了。

如果遇到稍微麻烦一点的case,希望模块可以多做一些事,最好模块也能办了,比如

4) 给模块发点指令,并立即执行,比如:

通过总线读取硬件数据

通过总线控制硬件

控制GPIO输出

读取GPIO输入

屏幕显示内容

扬声器发生

获取系统时间

5) 定时执行指令

精确到秒。 通过内置的表进行存储,可以定时执行预置的指令,

6) 模块系统指令

重启

复位

休眠

简而言之,我需要的就是在原有的系统上加一个物联网模块前端,可以帮我完成通讯工作,如果能再让她尽可能的提供一些增殖服务那就更棒了。 相当于把模块当成一个带通讯功能的协处理器。 我惊讶的发现,这个工作没人做,这么大一个烧饼,我决定捡起来,吃掉。

项目计划:

菜鸟学写Cat1 Demo集(连载)

基于Luat架构开发一套新的自有指令集,通过Socket或者串口可以收发指令,指令集采用极简单的指令,尽可能多的完成上述描述的功能。

硬件平台:

前不久在B站直拨单时候我做了一个开源的电话机,顺路就做了一款核心板,功能比较简单,刚好用来适配极简指令集

菜鸟学写Cat1 Demo集(连载)

项目周期:

讲真,我对这个项目能不能开发出来,能多久开发出来都没有任何的计划。写这个文档的目的,也是通过写作敦促自己能够每天坚持项目更新。 也希望您能多多督促,帮助我,教导我完成这个项目,先谢谢了!


转载请注明原文链接:菜鸟学写Cat1 Demo集(连载)
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址