2021年12月

文档

amazon官方文档, 中文好评!https://docs.amazonaws.cn/freertos/index.html
官网: https://freertos.org/
官方文档,英文的
AWS上的文档,也是英文的

FreeRTOS与RT-Thread对比: 后者自带很多第三方协议, 如i2c/mqtt等等, 而FreeRTOS似乎要自己找自己组装进去.

根据其他乱七八糟文档的整理

根据深入浅出,FreeRTOS新手+入门学习笔记整理.

  • 任务:
    xTaskCreate建立多个任务, 然后使用vTaskStartScheduler调度任务函数. 任务函数执行完成任务后应该进入等待.

  • 队列:
    xQueueCreate建立队列, xQueueReceive接收队列.

根据FreeRTOS操作系统最全面使用指南 整理
似乎就主要3个功能: 任务调度/FIFO队列通讯/内存管理.
时间管理主要就是一个延迟函数vTaskDelay

下面看官网教程https://www.freertos.org/FreeRTOS-quick-start-guide.htmlDeveloper Docs部分. 描述Kernel部分.

编码标准

FreeRTOS用的是MISRA标准.

前缀

变量类型 前缀
uint32_t ul
uint16_t us
uint8_t uc
有符号非标准int(如Type_t/TickType_t/size_t) x
无符号非标准int(如UbaseType_t) ux
enum类型 e
* 指针 p
函数类型 前缀
文件内函数(类似private) prv
void空返回函数 v
其他返回值函数 参照变量前缀

宏也有前缀, 应含有或部分含有所在的文件名(小写).其他部分为大写, 如: configUSE_PREEMPTION, 习惯了全大写的看起来怪怪的

移植层定义的适配类型

  • TickType_t:如果configUSE_16_BIT_TICKS为非零(条件为真),TickType_t定义为无符号16位类型。如果configUSE_16_BIT_TICKS为零(条件为假),TickType_t定义为无符号32位类型。注:32位架构的微处理器应设置configUSE_16_BIT_TICKS为零。
  • BaseType_t:定义为微处理器架构效率最高的数据类型。比如,在32位架构处理器上,BaseType_t应该定义为32位类型。在16位架构处理器上,BaseType_t应该定义为16位类型。如果BaseType_t定义为char,对于函数返回值一定要确保使用的是signed char,否则可能造成负数错误。
  • UbaseType_t:这是一个无符号BaseType_t类型

其他风格

  • 坚决不用 //注释, 哈哈. 一行80列以内.
  • 4格缩进
  • #include的顺序是标准库/自己的库/硬件特定的库
  • #include后面依次是#define, 文件内函数声明(都加了static), 文件内全局变量声明, 函数定义
  • 所有函数定义都有/*-------*/分行

任务task

FreeRTOS要做的跟Windows一样, 一个任务是一个窗口, 有自己的上下文和环境, 看上去就像独立运行的一样. 多个任务看起来就像是同时在运行, 实际上由任务调度去决定任务的切换, 每一时间只有一个任务占领CPU(当然Windows也是一样的)

任务状态

占领CPU的时候就是 running态, 暂时调度给其他任务就是ready态.
任务自己进入等待而把CPU主动让出来交Blocked态, 如调用了vBlockDelay()或等待其他事件. 等到了就恢复ready,或者实在等不到(超时timeout了)也恢复ready
啥也不干的任务可以调用vTaskSuspend()进入Suspended态, 跟Blocked区别就是不会超时. 只有调用xTaskResume()才会回到ready态.

任务优先级

数字高的先运行. 0是最低数字. 当前运行的就是最高优先级的一个或多个ready任务. 多个任务同时的话就用时间片切换.

优先级原则

  • Fixed priority固定优先级. 调度器不会更改优先级
  • Preemptive抢占式. 最高优先级永远先运行. 如果一个任务优先级被中断由低调到最高, 它立刻把抢到CPU, 无论其他任务运行到哪儿了, 即便是一个时间片没有执行完.
  • Round-robin同优先级循环切换
  • Time sliced时间分片.一个tick中断一片时间.

要小心饿死低优先级任务

高优先级永远最先运行, 如果占着CPU不放, 低优先级永远不会运行. 所以要高风亮节, 等事的时候就把CPU让出来, 进入Blocked或者Suspended, 事儿来了再处理.

优先级其他

抢占式和时间分片这两个特性可以在FreeRTOSConfig.h中关闭.
另外支持大小核(AMP)和同等多核(SMP)等CPU

任务实现

    void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
            -- Task application code here. --
        }

        /* Tasks must not attempt to return from their implementing
        function or otherwise exit.  In newer FreeRTOS port
        attempting to do so will result in an configASSERT() being
        called if it is defined.  If it is necessary for a task to
        exit then have the task call vTaskDelete( NULL ) to ensure
        its exit is clean. 
        翻译: 任务别想着return, 新的port里这样做会导致configASSERT()
        调用报错. 如果非得退出, 调用vTaskDelete( NULL ) */
        vTaskDelete( NULL );
    }

函数形态咯. for(;;)的意思是任务一直执行, 直到vTaskDelete, 相当于windows的×. 任务里面是坚决不能有return的, 否则就会报错.
一直执行的任务里面, 自己决定是不是要Blocked或者Suspended把CPU让出来. 所以这个for(;;)在裸机系统里面看起来像是个死锁, 实际上却是让任务保持运行的方式, 毕竟它不能return
任务函数的类型是TaskFunction_t

    void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
            /* Psudeo code showing a task waiting for an event 
            with a block time. If the event occurs, process it.  
            If the timeout expires before the event occurs, then 
            the system may be in an error state, so handle the
            error.  Here the pseudo code "WaitForEvent()" could 
            replaced with xQueueReceive(), ulTaskNotifyTake(), 
            xEventGroupWaitBits(), or any of the other FreeRTOS 
            communication and synchronisation primitives. 
            翻译: 下面伪码展示了任务等待事件的方式. 事件发生了就处理事件,
            超时了就处理报错. 伪码WaitForEvent可以换成 xQueueReceive(),
            ulTaskNotifyTake(), xEventGroupWaitBits()或其他通讯同步机制 */
            if( WaitForEvent( EventObject, TimeOut ) == pdPASS )
            {
                -- Handle event here. --
            }
            else
            {
                -- Clear errors, or take actions here. --
            }
        }

        /* As per the first code listing above. */
        vTaskDelete( NULL );
    }

任务使用 xTaskCreate() xTaskCreateStatic()创建, 使用vTaskDelete()销毁.

Co-routines协程

超小RAM的MCU可以用协程, 32位的CPU一般就用不到了. 这说的是Arduino的那个8位机吗? 这儿也不介绍了, 一般这种情况估计都用裸机了.

任务/中断间通讯机制

Queues队列

  • 队列是任务间以及中断和任务间的主要通讯方式. 采用的是先进先出FIFO, 跟排队一样, 先到先得.
  • 发到队列的数据是复制的, 这和其他消息机制()类似, 也就是发出去以后, 不管对方收了没有, 自己这一份数据都可以改了, 类似于函数调用的实参传递.
  • 中断使用队列和任务使用队列的API不一样, 方便识别.
  • 读取空队列的任务将进入Blocking态, 直到收到消息. 发送消息给满队列的任务将进入Blocking态, 直到队列有空.
  • 中断不允许调用以fromISR结尾的API函数. ISR意思是Interrupt Service Routine中断服务事务,就是中断处理函数.

Binary Semaphores二进制信号量

先说啥是信号量. 简单地说就是解决资源占用的方式, 跟十字路口的信号灯一样. 拿到绿灯的占用路口走, 给另一个方向的车辆红灯, 另一个方向的就等着(Blocking).有限的资源多个任务都想用, 就用信号量来解决. 也有点像停车场剩余车位数(计数信号量). 没车位的都在外面等着(Blocking).
这儿这儿有中文讲解.
互斥和同步. 互斥就是一个球只有一个人拍, "排斥"了其他人拍球, 想玩的排队. 同步就是想做才得先小工洗菜,再大厨炒菜,再跑堂端菜, 都是菜, 但就是得按顺序处理, 没到操作步骤的也排着队等.这儿看到同步也是一种互斥, 除了等资源, 还得步骤到了.
我在处理腾讯连连的MQTT时可能就是一种同步机制, 需要按顺序处理ready->GOT IP->时间->MQTT.只不过我是用enum的不同状态表示的.

二进制信号量就是占用时加锁, 释放时解锁, 表明这两种状态的.

Task Notifications任务通知有时候可以替代二进制信号量, 并且更轻量一些.

  • 二进制信号量同时用于同步和互斥机制.
  • 二进制信号量和mutexes互斥量很相似, 其差别只在于互斥有优先级继承, 而二进制信号量没有. 因此互斥量更适合互斥场景, 而二进制信号量更适合任务间(包括中断)同步场景.
  • 二进制信号量相当于只有1位的队列Queue.
  • 中断处理后才能执行的任务可以等待这个信号量, 而中断给出这个信号量,函数是xSemaphoreGiveFromISR()
  • 任务的优先级可以保证获取信号量以后的处理顺序(同步机制?).

Counting Semaphores计数信号量

同样 Task Notifications任务通知有时候做轻量级替代.

我在MQTT处理对pub消息的回应时用了类似的计数机制. 为防止在连续发出的第二条pub指令之后, 实际执行pub之前, 收到了上一条pub指令的success消息, 将pub指令取消导致第二条pub没有执行(同时还要兼顾失败重发机制), 我在pub发出指令后增加计数/在success时减少计数, 只有计数为0时才完全停止pub.

  • 相当于长度大于1的队列
  • 可用于事件的计数. 计数值就是未处理事件的值
  • 可用于资源管理. 跟停车场一个道理, 计数值表示空余车位的数量. 车走了加1车进来减1, 为0就等着.

Mutexes互斥量

用于对有限资源的访问.
互斥量借用完要自己还回去(只有1个且不会被消耗), 而二进制信号量用完不用还(持续被生产出来并且被消耗掉).
低优先级的任务持有互斥量时, 高优先级的再去获取, 会把低优先级的任务的优先级暂时提高到高优先级.
适合任务间确认谁访问资源, 不适合中断使用. 因为中断是不可能去等待任务给他返还可用性的.

Recursive Mutexes递归互斥量

可以被一个任务连续借用多次, 但也要被还回去这么多次, 其他任务才能用.

Task Notifications任务通知

算是新特性, 从v8.2.0开始引入, 在v10.4.0中增加了单任务多通知功能

  • 每个任务有1个或1组(v10.4以后)通知. 任务A可以发直达信息(direct, 一个32位的值)到任务B的通知里, 发到以后任务B的通知会变成Pending.
  • 任务B可以等待通知的Pending时Blocking.
  • 任务A发了direct后还能更新direct的值. 可以不管任务B读了没有, 也可以等待任务B读完再更新, 可以改1位或多位, 也可以递增
  • 任务B调用xTaskNotifyWait()/xTaskNotifyWaitIndexed()读了值以后, 通知状态就会变成Not Pending.
  • 任务B也可以直接调用xTaskNotifyStateClear()/xTaskNotifyStateClearIndexed()清除通知的Pending态
  • 即便有v10.4以后有一组通知, 任务B同时也只能等一个通知的Pending, 而不能同时等多个通知的Pending
  • 通知比之前那些方式要快45%, 并且使用RAM更少, 只是使用的时候有些限制: 1. 只有1个接受者; 2. 发送者一次发不完的时候不能block;
  • 任务A使用xTaskNotifyIndexed() xTaskNotifyGiveIndexed()发通知. 通知进入Pending态, 直到任务B调用 xTaskNotifyWaitIndexed() ulTaskNotifyTakeIndexed(). 函数名里面的Indexed可以去掉, 就对应的是Index==0的通知(感觉是为了保持版本的兼容性)
  • 通知可部分替代二进制信号量/计数信号量/事件组/邮箱机制. 官方文档对此有专门章节做示例

Stream & Message Buffers流和消息缓冲区

10.0.0以后才有的特性

TBD. 这么新的特性估计暂时用不到, 晚些在研究.

Software Timers软时钟

  • 是可选功能, 需要自行开启
  • 分一次性时钟和自重启时钟
  • 在时钟未溢出前还可以Reset时钟以重新计时

Event Bits (or flags) and Event Groups 事件位(标志)和事件组

  • 事件位指示事件是否发生, 一组事件位就是事件组
  • 可以在事件位变成1以前等待

静态和动态内存分配

  • 很多前面说的对象都可以动态或者静态分配内存. 动态分配就是在运行时再分配, 静态分配就是在link的时候就分配好,还能指定分配的地址.

heap堆管理

最新版本有5种堆管理方式.
这部分感觉暂时也用不上, 看原文吧.

栈溢出检查

新建项目

直到vTaskStartScheduler()调用后, 程序才表现得像个RTOS的样子.

调试

百度搜索了资料, 似乎只能通过打印信息调试, 搜索"FreeRTOS 断点调试"找不到结果.
Bing搜索"FreeRTOS breakpoint debug"还是能找到几条使用Keil设置断点调试的方式, 主要是说要在task里面设置断点, 并且不能用步入模式调试.
我设想一种调试方式, 即只针对task进行调试, 把task当做一个系统. 把FreeRTOS的API函数和硬件函数全部Stub来调试逻辑. 剩下的硬件逻辑和OS相关逻辑通过真机printf调试. 当然单元测试应该是可以用的.

芯片

主要系列是按CPU划分的.
其中ESP32和S2/S3/C3已经是不同的产品了, 这样命名挺讨厌, 自己既是大类的名称又是小类的名称...

芯片系列

系列 CPU
8266/8285 Xtensa L106
ESP32 Xtensa LX6 单核或双核
ESP32-S2 Xtensa LX7单核
ESP32-S3 Xtensa LX7双核
ESP32-C3 RISC-V

芯片后缀

后缀 意义
FHn n MB的flash
Rn n MB的PSRAM
FNn n MB的Flash

多年来都是瞎打,中午虽然有半小时出汗,也还想多获得一点儿乐趣。

提升命中率

视频: https://www.bilibili.com/video/BV1ux411L73q
要找到适合自己的舒服的投篮姿势. 每个人都是不同的.

  • 身体侧对篮筐而不要正对篮筐. 投篮侧的脚在前指向篮筐, 可减轻肩膀压力有利于命中.
  • 脚后跟不能着地而要脚尖着地跳投. 跳投前脚后跟接触地面会泄力. 脚尖着地也能增加射程
  • 跳投双脚前摆(而不是落在原地)能让出手点提高弧度提高从而提高命中率
  • 投篮手部姿势从起手位就要弯好,而不是要发力投球的时候再后弯手腕和准备其他手指
  • 下盘动作可以越快越好, 目标是最快速度踩到舒适投篮位置, 做好舒适投篮动作
  • 上盘动作要越舒展越好, 要保持固有节奏, 不应因为要着急快速投篮而肆意加快上盘动作, 导致投篮失准
  • 将球快速拉到自己舒适的起球垂面上, 藏于篮球口袋(大概意思就是在衣服下口袋的位置?), 并沿着垂直面向上起球. 同样不能因为着急而不沿着自己熟悉的投球线路起球.

总结: 投球要准就是要将身体准备姿势放置到最舒适位置才投.

AHK用了很久却一直没有深入学过, 今天深入一点写个入门.
AHK不比什么RPA工具效率低. 最大的缺失可能是元素识别.

五种语句体

;0. 自动执行的,跟js一样写什么执行什么
Some code
return

;1.定义快捷键(热键)的
^j::
  Some Code
  Return

;2. 定义热字符串的
::btw::
  Send, by the way
  return

;3. 定义子程序的. 相当于汇编里面的label, 然后用gosub调用(跟goto有点像)
subName:
  Some Code
  return

;4. 定义函数的
function(param1, param2:="xxx"){
  Some Code
  return "A value"
}

多文件组织

可以用#include包含其他ahk文件

传统语法

ahk说明里面讲到ahk历史悠久,所以有一些传统语法(就是看上去有些恶心的语法), 比如说某些命令里面字符串可以不加引号让你搞不清哪儿是字符串开始和结尾...

基础语法

; 单行注释
/*
多行注释
但有趣(烦躁)的是, /*和*/必须在行首. 像这一行的这俩是不起作用的
*/
"这是一个字符串, 需要双引号" 
变量 := 赋值

讲讲字符串里面的转义. C语言是用\转义, AHK比较变态使用<code></code>转义,搞得我markdown都得写code标签. 主要转义有%t rn. %用于创建动态变量引用和动态函数调用, 有点让我想起来.bat格式
但是但是但是, "的转义是"", 很神奇吧, 那就是说如果要表示一个引号字符串,加上把它括起来的引号实际上是4个引号哈哈哈哈""""

+-*/(), 和++ -- += -=等运算符都支持, condition?value1:value2的三元运算也支持.
:=是赋值运算符, 不太一样. and or not是逻辑运算符

支持类似js的对象, 可以用这样的obj := {Key1: Value1, Key2: Value2}形式创建, 以obj.Key1的形式访问
数组也是[]形式的, 但是第一个成员的序号是1而不是0

Ctrl键未绑定的快捷方式

按键:

软件 ABCDEFGHIJKLMNOPQRSTUVWXYZ 12346890 F123456789⒑⒒⒓
EXCEL JMQ F1 F2

Excel参考: https://www.asap-utilities.com/excel-tips-shortcuts.php

自带的常用ahk

Windowspy.ahk 用于查找句柄ClassNN

部分常用命令

; 设置鼠标坐标为屏幕
CoordMode, Mouse, Screen

; 按窗口标题名称点击登录
ControlClick, X161 Y489, 系统登录

; 点击坐标
Click, 123 456

; 激活不在前台的窗口
WinWaitActive, ahk_exe EXCEL.EXE

; 停1000ms
Sleep, 1000

; 发送alt+F4
Send, !{F4}

; 发送字符串并且不激活输入法
Send, {text}some string

; 运行程序
Run, "D:\Program Files\网店管家云端版\wdgjyun.exe"

; 创建函数
saveExcel(filename)
{
  ; 删除文件
  FileDelete, %filename%
  Sleep, 1000
  send, {F12}
  Sleep, 2000
  send, {text}%filename%
  Sleep, 1000
  send, {Enter}
  Sleep, 1000
  send, {Enter}
  Sleep, 2000
  Send, !{F4}
}

; 点击图片. x1,y1 ~ x2,y2是图片的搜索区域. delta是点击位置与图片左上角在xy坐标上的偏移位置
imageClick(x1, y1, x2, y2, imageFile, delta){
  ImageSearch, FoundX, FoundY, x1, y1, x2, y2, %imageFile%
  if (ErrorLevel = 2)
      MsgBox Could not conduct the search.
  else if (ErrorLevel = 1)
      MsgBox Icon could not be found on the screen.
  else{
      MouseClick, left, FoundX+delta, FoundY+delta
  }
}

Arduino方式开发

NonOS开发

FreeRTOS开发

ESP-AT开发

乐鑫已经停止了对8266 AT开发的支持, 后续只维护BUG. 现在乐鑫推荐 ESP32-C3也即是RISC-V平台.
ESP-AT Git项目能找到8266最后版本的技术文档

ESP-IDF开发

官方文档是这个: ESP8266 SDK的开发指南, 目前只有英文

CSDN上半颗心脏同学的8266开发笔记
乐鑫关于8266和32开发用IDF的区别, 8266 SDK从V3.0开始与IDF风格统一, 但由于不是完全兼容, 所以名称没有改为IDF.
ESP8266 SDK的GIT库
ESP-IDF的GIT库

环境搭建

还挺费劲, 下载的东西还挺多

准备工作

  1. 大多数人可能还是用windows 环境吧
  2. 需要下载3个包, 1个是msys2模拟linux环境, 1个是8266 编译器, 1个是8266项目包
  3. 需要用到github,所以准备好梯子

具体步骤

  1. 下载并解压 toolchain: https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20181001.zip, 解压缩后把里面的msys32放到一个简短点儿的目录里比较好, 如D:\msys32
  2. 双击里面的mingw32.exe, 这就是以后的主要工作环境了. 先在home\用户名\也就是~目录下建立一个esp目录, 把开发支持工具(等会儿会有2个), 放在这儿. home目录就在D:\msys32下面.
  3. 下载第一个开发支持工具: esp8266 toolchain, https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32.zip, 解压缩到esp目录里面.
  4. mingw32里面用vim .bashrc增加一句话export PATH="$PATH:$HOME/esp/xtensa-lx106-elf/bin"
  5. esp目录里下载第二个支持工具. 这儿要用gitgithub. 所以你可能要先在.bashrc里面设置个代理export http_proxy="192.168.0.1:8080", 然后重启mingw32, 进入esp目录运行: git clone https://github.com/espressif/ESP8266_RTOS_SDK.git , 也可以下载zip包https://github.com/espressif/ESP8266_RTOS_SDK/releases/download/v3.4/ESP8266_RTOS_SDK-v3.4.zip直接解压, 然后把clone后的路径设置为系统变量IDF_PATH. 可以在.bashrc里面设置, 增加一行: export IDF_PATH="$HOME/esp/ESP8266_RTOS_SDK",也可以再windows系统的环境变量里面设置, 如果在winows环境变量设置, 值要设置为/home/用户名/esp/ESP8266_RTOS_SDK这样的形式. 似乎~符号不认. 重启mingw32使设置生效.
  6. 运行 python -m pip install --user -r $IDF_PATH/requirements.txt
  7. 复制一个hello world出来cp -r $IDF_PATH/examples/get-started/hello_world
  8. 进到hello_world目录运行make menuconfig, 出来个UI界面配置一下串口名称如COM18, 保存退出. 注意这个是ESP8266的烧录串口, 波特率74880
  9. 运行make flash, 会检查环境, 并且从github下载项目, 编译, 挺花时间, 没个十几分钟搞不定. 可以增加一个-j8参数, 使用8核编译,速度会快很多
  10. 运行make monitor可以监控串口输出.

好了, 看起来终于可以开始了.

其他有用的命令

make all 只编译而不烧录, 也会在最后显示烧录命令.
make print_flash_cmd 打印烧录命令, 可以用在其他地方
make partition_table 打印分区表, 一般分为两种: 非OTA的分区表Single factory app, no OTA和OTA分区表Two OTA app, 可以在make menuconfig中选择.

分区表

分区表烧录在0x8000位置, 长度为0xC00
非OTA的分区表:

# Espressif ESP8266 Partition Table
# Name,   Type, SubType, Offset,  Size
nvs,      data, nvs,     0x9000,  0x6000
phy_init, data, phy,     0xf000,  0x1000
factory,  app,  factory, 0x10000, 0xF0000

NVS是非易失存储, PHY似乎是物理什么的
OTA的分区表

# Espressif ESP8266 Partition Table
# Name,   Type, SubType, Offset,   Size
nvs,      data, nvs,     0x9000,   0x4000
otadata,  data, ota,     0xd000,   0x2000
phy_init, data, phy,     0xf000,   0x1000
ota_0,    0,    ota_0,   0x10000,  0xF0000
ota_1,    0,    ota_1,   0x110000, 0xF0000

可以看到OTA分区表是兼容非OTA分区表的, 兼容性明显比以前的8266项目好多了. 网上有很多介绍以前的8266分区表的, OTA分为user0/user1, user0起始于0x1000而不是现在的0x10000, 现在应该已经不适用了.
otadata是指示启动那个app的, 如果为空就启动ota_0
分区表可以自定义, 不过我感觉一般是没必要了

系统任务

大写T在最后面的是IDF的任务, 其他的是FREERTOS的任务.

uiT 用户初始化任务, 初始化完以后调用app_main再销毁自己
IDLE FREERTOS的空闲任务, 在其钩子vApplicationIdleHook中调用sleep和喂狗任务
Tmr FREERTOS的软时钟
ppT 处理WIFI硬件驱动,我猜是process protocol的缩写
ppT 系统电源管理, power managerment.
rtT 高优先级的硬时钟中断任务. 主要处理WIFI实时事件. 基于这个组件的程序不要在应用(application)里面调用,以免影响WIFI收发.
tiT Tcp-Ip协议栈的任务(LwIP), 处理TCP-IP包.
esp_event_loop_task 处理系统事件
优先级从低到高依次是:

0 IDLE 
2 Tmr 
8 tiT 
10 esp_event_loop_task
11 pmT
12 rtT
13 ppT
14 uiT

用户任务的优先级要低于rtT也就是12.
如果要加快 TCP/UDP 吞吐量,可以尝试将发送/接收任务的优先级设置为高于“tiT”任务的优先级(8)

PWM和嗅探器Sniffer共存问题

SmartConfig应该属于一种Sniffer.
8266是没有硬件PWM的, 它是用硬时钟模拟的软件PWM. 硬时钟同时用于WIFI, 所以WIFI sniffer和PWM同时用会出现资源争用的问题.
N个通道的PWM每次翻转GPIO将占用6+8N ns的时长. 例如一个通道PWM就是14ns.
占用期间, 如果收到了必须要实时处理的LPDC或HT40包, 这些包就会被丢弃.
同时使用PWM和smartconfig会使得配网时间更长. 而且调整PWM的频率/duty cycle/phase都会影响smartconfig的速度.
如果要同时使用, 需要:

  • PWM 的频率不能太高,最多 2KHz。
  • 修改PWM的占空比和相位,使每个通道反相之间的时间间隔(Tn)等于0或大于50us(Tn = 0,或Tn > 50)
    这样看起来这个软件PWM似乎不太适合用于LED驱动~~

使用API-Reference文档

非常神奇的, 8266仿照IDF风格的API编程手册, 乐鑫已经放弃治疗了, 8266 RTOS SDK 编程指南最后版本V3.4中, 居然只是把每个函数罗列了一遍, 有个球用. 找了网上各种指南, 发现只能参考ESP32的手册, 在同样的目录结构下, Uart的使用就有非常详细的描述.

使用 make menuconfig工具

感觉有些操蛋~ 很多在程序里面定义的参数还要再menuconfig里面预先定义一下, 否则程序编译后运行得总有些问题. 而且make menuconfig重新定义以后,这个编译速度就慢的惊人了

使用linux服务器或者使用docker中加载linux环境, make的速度可以快好几倍.

menuconfig主要是在根目录下生成sdkconfig文件作为编译依据. 同时会把旧的文件改名为sdkconfig.old. 如果熟练的情况可以直接修改sdkconfig文件.

是否要重新编译, 似乎是根据sdkconfig有没有更新决定的. 所以即使修改了一个和编译完全无关的项目, 如PC烧录串口, 也会导致全部重新编译.

IDF版本兼容性

IDF各个版本兼容性不佳, 如8266 v3.4和v3.1是不兼容的. 发现的就有 uart_driver_install在v3.4比v3.1多一个参数,导致编译不能通过.

8266的串口输出

8266只有1.5个串口, 也就是1个完整串口UART0和1个只有TX的串口. 所谓有时候看到的UART2(IO13/15)是UART0交换(swap)过去的.

基于idf, 函数中直接使用printf函数将直接从uart0口输出, 不需要串口初始化的步骤.

  • 在menuconfig中将Swap UART0 I/O pins选上, 编译烧录后, 刚刚启动打印的boot信息在UART0输出, 后面printf的信息都在UART2上输出. 如果使用了软重启esp_restart();, 软重启后的boot信息也将在UART2上输出.

  • 在menuconfig中将Uart for console output选项改为Custom, 如果UART peripheral to use for console output (0-1)仍然为UART0, 结果和上面的一样不变.

  • 如果将UART peripheral to use for console output (0-1)改为UART1, 那么第一次启动的boot信息在UART0输出, printf的信息在UART1输出, 软重启后的boot信息在UART2输出.

  • 在meunconfig中将Partition Table改为Factory app, two OTA definitions不会对运行结果有什么影响, 但是flash烧录的固件名称会改为partitions_two_ota.bin
    如果是单文件, 烧录命令是(project_template是项目的目录名):

    D:\r\esp\esp-idf\components\esptool_py\esptool\esptool.py --chip esp8266 --port COM18 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 2MB 0x0000 build/bootloader/bootloader.bin 0x10000 build/project_template.bin 0x8000 build/partitions_singleapp.bin

    如果是ota,烧录命令是:

    D:\r\esp\esp-idf\components\esptool_py\esptool\esptool.py --chip esp8266 --port COM18 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 2MB 0x0000 build/bootloader/bootloader.bin 0x10000 build/project_template.bin 0x8000 build/partitions_two_ota.bin
  • menuconfig只能控制所谓console的uart设置, 如果要使用uart,还是要自己通过程序进行uart初始化设置.

  • uart初始化使用uart0的影子uart2, 如果在menuconfig中已经设置了swap, 在初始化中就不需要再次调用swap函数了.

    static void uartInit()
    {
      // Configure parameters of an UART driver,
      // communication pins and install the driver
      uart_config_t uart_config = {
          .baud_rate = 115200,
          .data_bits = UART_DATA_8_BITS,
          .parity    = UART_PARITY_DISABLE,
          .stop_bits = UART_STOP_BITS_1,
          .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
      };
      uart_param_config(UART_NUM_0, &uart_config);
      uart_driver_install(UART_NUM_0, BUF_SIZE * 2, 0, 0, NULL);
    }
  • v3.1printf的问题: 有时候不会及时输出, 尤其是在使用了格式化符号%d%x等等后, 必须要用fflush(stdout)后才能输出.

include顺序

idf头文件#include是有顺序的,否则编译不通过.
感觉顺序似乎应该是标准库文件->freertos头文件->esp系统文件->驱动文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "driver/uart.h"

  1. 签名要用Adobe Reader XI 11.07版本. 不多不少.
  2. Adobe Reader XI 已经被Adobe官方停更了, 也就是在官网下载不到咯, 目前官网最新版是 Reader DC.
  3. 但你不能用Reader DC, 因为很多CA证书仍然是使用SHA1加密,但Reader DC因为其不安全已经不支持了;
  4. 好吧,其实DC是支持的,只不过要改注册表,然而他还不推荐你改,就不好好给你提供个地址让你去改;
  5. 然后你还是要退回到 Reader XI 11.07版本, 安装的时候记得所有都要点同意。杀软要是拦截一定得放行了,否则你会在签名的时候遇到奇妙的闪退问题;
  6. 是的,你不得不用不安全的落后的CA证书和已经被弃用的Reader版本来完成签名。X蛋不?

最近用一款51单片机与8266(AT固件)的UART2接口TTL串口通讯, 使用115200的波特率总是每70多个字节或100多个字节出现误码, 8266使用的WROOM-02D模块, 26MHz晶振, 而51使用的是RC震荡.
具体误码的情况是: 8266发送到51不会误码, 而51发送给8266就会误码. 使用电脑HTerm查看, 8266只要启动了AT固件, 51发送的就会误码, 8266不正常启动(如进入烧录模式)或者启动一个啥也不干的空程序, 就不会误码. 怀疑是8266初始化UART2串口以后才有的问题.
尝试了如下各种方法都失败了:

  • 尝试将51的波特率降低一半到57600, 仍然误码.
  • 尝试将51的波特率改一点点如115201, 结果根本就读不到数据, 这不科学! 难道是硬件串口内置了集中固定的波特率? 因为波特率都需要时钟发生器, 这倒是很有可能无法改一点点.
  • 尝试电脑端更改波特率, 发现大约在(115200-3000, 115200+6000)左右都能正确接收, 超出一点儿之后会出现误码,超出太多就全部乱码了. 算下来容忍度有8%左右.

参考了这篇文章, 里面讲到一次不要发的太多. 考虑到每次发送都是在相对固定的位置误码(70个字符以后), 最后成功的方法是:

每次发送50个字符后, 延时5ms再继续发送.

延时时长是反复测试得出的.测试发现每发送50字符延时2.5ms正好可以不误码, 为了容错将这个时间加大一倍为5ms.
硬件上继续尝试解决, 看是否能够找到最终原因.

久了不用就会忘
参考: https://zhuanlan.zhihu.com/p/85383698

  • A的所有B后代: .A .B{}
  • A的所有B子代: .A>.B{}
  • A后面一个邻居: .A+.B{}
  • A所有邻居: .A~.B{}
  • A和B的交集: AB{}, 选择这样的元素: <div class="A B"></div>
  • A和B的并集: A,B{}, 选择这样的元素 <div class="A"></div><div class="B"></div>

b站这个9分钟教程很好https://www.bilibili.com/video/BV12J411z7j7?p=1

原理图

基本操作

  • 新建工程(图标:左上角的本子)
  • 新建原理图(原理图编辑器)
  • 图框设置(图标:一张纸上有个坐标轴): 设置信息
  • A键, 选择元器件, 其中: C是电容, R是电阻, 一般的接口在Connector_generic中, USB在connector中
  • 放好元器件, 按M移动并调整位置, R旋转, X垂直镜像, Y水平镜像
  • P放置电源和地线. 如5VGND. 对于电源和地线,还需要选择两个PWR_FLAG并分别连接到电源和地线上
  • W键进行连线
  • C可以复制当前指向的元器件
  • 直接左键框选可移动框选住的目标, 按Shift+左键框选可以复制框选目标并移动
  • 连线后按M键移动元器件, 连线不会跟着动, 这是需要按G键, 可以带着线移动元器件
  • V键可以修改元器件的值, 按E键也可以改
  • L键可以为线增加标签, 相同标签的线认为是连在一起的

自动序号

标注原理图(图标: 一张纸上一个铅笔), 点击标注就可以给元器件自动上符号

设置封装

编辑符号字段(图标: 一张表格), 在对应的元器件的Footprint栏里面, 点击后面的书架样子的图标, 打开封装库, 选择封装. 选择完封装才能做PCB.

检查

点击执行电器规则检查(图标: 瓢虫), 点击运行即可以看到检查到的问题.
修改后再次检查直到没有问题.

PCB

在原理图上按F8键, 点击更新PCB,即进入PCB编辑器, 鼠标上带着所有元器件. 单击放置.

基本操作

  • 设置原点. 点击右侧设置网格原点(图标: 4x4个点带1个红点), 选择要画的PCB的左下角
  • 在右侧的中选择Edge.Cuts即PCB刀切层, 选择后会有个小三角指向这个层
  • 点击添加图形线条(图标: 4个点3条线连着), 在绘图区点击右键, 选择网格中合适的网格大小, 如1.0mm, 从上面的原点开始画图, 画一圈边线连接起来.
  • Margin层, 画在刀切层内0.3mm画一圈布线区域. 不能超过这个区域布线以免被刀切断
  • 在元器件上按M键可以移动, R键旋转. 但是XY是用不了的. X另有它用
  • 在元器件上双击, 弹窗中可以修改坐标数值精确定位
  • 放好器件后, F.FabB.Fab层可能干扰实现, 可以关闭.

布线

  • 布线前先定义线. 点击电路板设置(图标: 电路板上一个齿轮), 按需要设置设计规则, 网络类表, 导线和过孔, 阻焊/锡膏
  • 一对线的走法可以按Alt+6选择差分对布线, 选择后点击右键选择差分对标注并选择定义好的差分对值, 然后给一对管脚布线
  • 在管脚上点击X键可以进行单线布线. 布线的宽度可以在右上角下拉菜单里选择
  • 在布线中可以点击/键修改布线的方向, 在水平/垂直/45度方向间切换
  • 在布线中可以点击V键增加过孔
  • 布线后, 点击V键可以看背面的线
  • 布线后, 点击一条线后点击I键可以选择所有相连的线
  • 布线后, 点击D键可以不中断线的情况下平移线
  • 最后除了GND以外, 所有的线都布好
  • 点击Alt+3可以看3D图

标注和覆铜

(对应第三节: 出Gerber文件)

  • 选择F.SilkS层, 再点击添加标注(图标: 测距符号上有个N), 然后点击两个点就可以标注距离
  • 点击F.Cu层, 再点击添加填充区域(图标: 绿色底上有条过孔线), 点击覆铜区域的一个角, 弹出窗口, 选择合适的值, 确定后, 把区域画完,即可.
  • 在一个角上点右键选择覆铜->重复区域区域到层, 弹窗中选择B.Cu就可以给背面覆铜

官方原文: https://webpack.docschina.org/concepts/
Webpack似乎已经到了不得不深入学习的程度了, 要不然有的概念看不明白. 现在居然还有绕过npm install, 用webpack来引入外部库的方式!(PS: 通过后面的学习, 发现webpack确实可以在externals中声明CDN地址的方式来引入外部包)

Webpack是一个静态打包工具. 也就是从各种依赖里把需要的抽取出来, 形成bundle. bundle在webpack里就是的意思, 也就是"打包"的"包"
所有的配置都是在webpack.config.js中完成的
下面说的react和vue都指的是其cli工具

entry

是入口, 也被称之为chunk块. 默认是./src/index.js , 在react中是app.jsx,在vue中是./src/main.js,

output

是输出的bundle和其他文件的存储位置. 默认是./dist/main.js, 文件位置在dist文件夹中. react和vue都没有改这个文件夹.

loader

是处理除了.js.json文件打包以外, 其他文件的打包工具. 包括.css, .txt等等. loader是在module.rules中以数组的方式规定的. 每个数组元素包括以正则定义的后缀名test(不知道为啥叫test),和loader名.

plugin

执行除了loader以外的更宽泛任务. plugin需要通过npm install安装后在webpack.config.js前面require进来,然后在plugins下面定义数组元素.

mode

有三种值: production, development, none, 是对开发环境的定义

模块的导入方式

所有导入方式都需要预先npm install这个模块

  • 静态导入, 就是常规的在文件开始import.
  • 动态导入, 在使用模块的函数中, 先使用import导入(异步方式), 再使用模块.
  • 预获取prefetch. 提前动态获取下一个页面要用的模块. 这是通过在js的注释/**/中添加webpack命令实现的.
  • 预加载preload. 在加载本页面前先加载指定模块. 与上面同样是在js注释中实现.

外部化 externals

在创建library时尤其需要关联外部库, 而不是把外部库也打包到自己的库里面. 这儿就是使用externals. 使用了externals之后, 打包bundle就会把import的外部库排除掉.

为生产环境配置不同的config文件

本来嘛, 默认的config文件是webpack.config.js,然而为了生产的打包文件更小, 开发的打包文件更方便/热更新等, 就需要配置两个不同的config文件webpack.prod.js, webpack.dev.js, 然后为了把两个不同的config中共同部分提取公约数, 又有了第三个公共文件webpack.common.js.
另外我又发现在react的项目中, package.json又有一种用一个webpack.config.js实现两种环境的方式. 区分时加上不同的命令行参数:

"scripts": {
    "dev": "webpack-dev-server --config webpack/webpack.config.js --env.mode=development --watch",
    "release": "webpack --config webpack/webpack.config.js --env.mode=production",
    "dev:dualmode": "npm run dev -- --demo=DualmodePanel",
    "release:dualmode": "npm run release -- --demo=DualmodePanel",
    "dev:wugan": "npm run dev -- --demo=NonInductiveDemo",
    "release:wugan": "npm run release -- --demo=NonInductiveDemo"
  },

externals

这个属性只是为了不让webpack去打包指定的内容. 然而在开发时, 这个内容还是要能访问到的. 不管是用npm install,或者在index.html里面用<script>引入, 还是说在externals里面直接指定CDN地址.