0%

起因

无聊摸鱼,突然看到一个进度条一样。我就想到了这个进度条是怎么实现的呢?我们下载软件,下载插件,下载东西,编译的时候都可能会出现tty终端的进度条。我就好奇了,cmd和shell那些终端这个命令行不应该是输出一行的吗?就像我串口打印出来的东西,不可能把我已经输出的东西,在显示端删掉啊,在重新打印一边的,肯定就是换行处理了,这个不换行又覆盖打印是怎么做到的呢?

区别

我们一般进行调试,有232也有串口进行对MCU、MPU、ARM-A板子的串口调试,这个时候,处理器是直接使用串口打印的,就是把串口缓冲区立马的数据输出出来,发送给我,而我在把他显示出来,因为,显示肯定是不能丢失接收数据和信息,所以,就会全部打印,换行打印。所以来一条打印一下,显示一条符合要求和调试。

这里就有一个误区了,因为我们使用tty的时候,还没输出出去哦!

这个什么意思呢?意思就是我们这个系统还没对外输出而是内部操作,此时tty不是一个串口输出的过程,而是,直接读取一个缓冲区内部的数据。没错,tty就是一个缓冲区,它不是日志系统,不会记录所有操作,记录操作是其他功能做的,它就是一个缓冲区,也就是说,我们做的一大堆操作,其实都是在缓冲区里进行的操作,此时都没对外输出,当然可以覆盖自己缓冲区数据,然后tty显示这个缓冲区数据。当然,其他的打印和日志系统就会把每个操作,每个情况,每个数据都记录下来啊。

实现

此时直接使用回车就可以,清空本行,重新覆盖输出了,就可以实现进度条的操作了。
这里还有一点就是回车和换行,回车是\r 换行是\n;
我们在学c语言的时候,肯定打印函数和输入函数也即是scanf和printf这两里面用了很多换行,也经常会发现他们两个的效果是很相似。这是因为发送完毕,换行会直接清空缓冲区,而回车是回到首行开头,这个就有点文件io的光标的味道了,缓冲区清空了,这个时候回车肯定就是原地蹦一下而已嘛。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

mark=''
for ((ratio=0;${ratio}<=100;ratio+=5))
do
sleep 0.2
printf "progress:[%-40s]%d%%\r" "${mark}" "${ratio}"
mark="##${mark}"
done
echo

总结

tty终端不是日志系统,它是一个收发缓冲区,它显示的内容是它缓冲区的内容,此时都没对外输出,所以它不会显示所有情况、数据、消息,只会显示缓冲区里有东西。此时,想要覆盖上一次的结果,echo就不要换行 -ne 此时直接回车到开头,然后清空本行,输出就好了,这个是只能在终端才能实现的哦。如果使用那种rt-thread那种FinSH控制台,这个就是串口输出啦,就是对外输出了,此时修改缓冲区是亡羊补牢了,因为缓冲区的数据已经被打印出来了,自然在显示端就会出现换行,而且无法覆盖上一行的操作,也无法实现进度条了捏,呜呜呜。

电源问题

这个问题,非常容易忽略,也就是,目标设备是否有外部电源或者内部电源。这个问题会影响到生产中的一些措施,设置连开发过程都会影响。

  • 一直使用swdio进行debug,却没注意到,这个swdio的vcc已经承担了供电的职责,可能会忽略没有供电的时候出现的异常bug。其实,内部线路,可能是有问题的,但是供电有了反而一切都是正常的,很可能硬件握手失败都开不了机呢。常见于热插拔设备上面。

  • 外部电源,这个电源是多大的,这个电源太大了怎么办,工人进行装配的会不会出现需要外部供电才能进行处理的操作,如何升级,如何操作等。

  • 内部电源,这个常见就是低功耗问题了,要知道一个设备的寿命,可以用多久,一般都是按照几年来处理的,一些物联网设备,或者可以外接电源的时候随便充电的策略。

  • 发热问题,这个其实很多时候也是会影响设备运行的,cpu和gpu那些都是要散热的,同理,你的温度可能会到值金属变形,可能导致电机行程异常,主控宕机,硬件损毁等诸多问题

生产顺序

这个问题,还是在电子件、模块、结构之间的顺序问题。

  • 后配置电子件,然后通过外部来进行标定和升级,这个常见于传感器和adc上面,因为只有结构装备完毕才能保证数据是正确无误的。
  • 先配置电子件,就是比如序列号啥的的,一些协议,数据,功能啥的,都不需要外部结构,而是可以通过模块来实现操作的,这个就不用担心外部的结构了,所以可以先配置好电子件,然后在安装。
  • 模块和结构,这个可以认为模块和结构合二为一,又或者说只有结构,模块就是普通的引脚电平啥的,这个时候就看是否是传感器类型的了,只是电平开关的话,就非常好处理了。
标准治具

也是一个生产规格的问题了,电机、adc、大小、孔径等,这些一系列的东西都是需要一个标准来检测和处理,这个时候就需要治具了

  • 电机治具,这个就是检测在行程之内的速度、电流、行程等处理,也可以在电机上面添加霍尔传感器来知道此时电机位置等方法,来确保电机是正常的,这样就组装了

  • 孔径治具,可以直接发射光束来作为标准,上面发送,下面接收,就知道误差范围了,这个治具设计就非常复杂和严谨,还得注意考虑灯丝温度和治具措施,一般都是购买国外进口的专门测试工具

  • 通信治具,写好一个设备,肯定需要快速检测,一般用于测试从机,因为工人是不懂代码的,只想快速验证。所以,一个比较简单,比较快速验证产品正常的东西是利于生产和检测的。

  • 老化治具,就是专门用来跑的,超频,提速,升温,升压,低温等处理,这个就是为了检测寿命的,一般是老化柜那样的东西,不过,想要自己做一个快速测试,也不难的。

  • 长度治具,这个就是尺子了,没啥好说的了。最多是来规定一些电机的运行行程,来保证统一。

    还有很多奇奇怪怪的治具。

测试日志

测试的时候,最好有记录和日志,这样方便bug复现和审查。

所以,串口数据,如果可以还是保留一下,linux的话,就是echo打印出来,重定向输出到专门的日志文件。这个就是看个人习惯了。运维的肯定就非常熟悉了,这个肯定是必不可失的部分,如果连复现都无法做到,这个bug也是根本不可能修复的。

电气规则

常见的ttl 232 485 can等那些电气特性,还有usb pcie m.2 等特有热插拔电气特性。

  • 就需要在生产的时候,注意设计防呆口,防止正负电源问题,还有设计一连串固定,唯一的组装流程,减少带来的外部干扰,还有带上静电环,人体静电还是有影响。

  • 还有就是开发中,可能开发板的电气规则可以工作,但是pcb板子就不能工作,很可能是电平范围错误,导致的通信异常,此时程序是可以运行的,但是因为外部缺少上下拉、电容、电阻、甚至是相位差等问题,导致的无法通信,需要和硬件协商,找到合适的方法实现通信。

产品生产环境

这个,实验室环境、无尘、温度、压强、电源等问题,都可能会导致传感器的误差,这些都是可以避免的,不过很多时候应该影响不大的,注意强磁强电、大噪声等环境问题、灰尘如果进入电路板很可能导致一些未知bug,最好也是杜绝为好。

设备寿命和老化

这个上面有一个老化治具的设计思路,其本质还是担心设备的耐久度,这个就非常值得考虑了,不能说,一个产品用两天就坏了,那谁还买啊,差评如潮。

但是,设备不是永久的,是一定有寿命的,一般是越是大型设备,寿命越长,越是消费电子,寿命越短,物联网设备,一般是几年左右,所以,这里就有内外电池的考虑,太阳能的考虑等

工作环境差异

设备工作区域,多热,外壳会不会化了,内部会不会积热,会不会进水,会不会受到干扰导致起振失败,导致内部emp,共振、振动环境导致螺丝和接口等松动、温度差异带来的传感器误差,电池寿命,电池电量这些都是非常大的问题,都是需要考虑进去的

焊接和贴片机

这个常常发生在pcb生产中,因为国产贴片机的问题太多了,抛料都是正常的现象,后续硬件工程师虚焊也是正常的东西,所以,就非常有必要治具了,确保电子件安装到产品设备之前是正常的。

安全和高效检测设计

这个就是比如,安装空调外机,安装复杂大型设备,不方便拆卸等情况,就需要留一个专门的接口给外部,外部调试线连接测试,就可以立刻知道设备当前状态如何,是否可以确保正常运行,这个高效,同时也一定程度上增加了安全性。这样可以高效抽查,极大程度缩短检测时间,提高现场检测的能力。

自然安全性这个就是要单独拿出来分析的,产品外壳毒性,电路板毒性,任天堂以前的卡带上面就有一些化学物质,非常非常苦,就是为了防止人去舔以及误食,这个也是一个出于安全的设计。外部结构设计是否安全,电路内部电容,电路线,电源线,dc-dc模块等问题。不能像机革那样,自己电脑炸了,还怪用户,至少得保证自己产品不得爆炸吧,三星爆炸手机可是深得人心。

运输结构设计

不能说一个设备长的和刀子一样吧,也就是说,运输的时候,要么就是有配套的外壳和箱子,要么就是设计成方便运行的结构,这个都是机械佬考虑的了,主要还是以应用场景和大小为主。

产品差异补偿

没人敢打包票说每个设备都是ok的,都是一模一样的,一定有残次品,这就是良品率了。优化生产过程,减少外界干扰,多用治具来保证产品的正常,增减检测工序,这个注定是没有最好,只有最适合了。

华为甚至更加过分,屏幕抽奖,一个手机的屏幕,几家厂商提供的产品,每个手机都想抽奖一样,这就导致了产品差异是非常大,如何软件进行调教,硬件去弥补,去做误差补偿,如色域、光亮、帧率等。这个问题是永远没有最佳方案的,都是就事论事,根据实际情况去分析和处理。

生产安全

这个是重中之重,想都不要想,只要出了一条人命和残疾,那不得了,不只是赔钱的问题,而是生产线都可能出现大问题,带来连锁反应,带来的负面影响都是不可估量的。

用电安全,设备摆放,工具摆放,特殊化学品处理区和存储区,禁止明火,不要堵路,地面防滑,尖锐突出,禁止打闹追逐,防夹,注意生产车间的机床注意实现,注意远离机床,不要为了效率而增加风险,一切高危都要做安全措施,不要靠近大型设备等诸多安全问题。

代码设计和能力需求

作为公司项目,肯定不是做着玩的,也肯定不会只有一个,所以,不同的设备就有不同的序列号和参数属性了。同时就要求有可以使用、可以修改的接口。至于使用c cpp rust python 还是什么,只要能实现功能就是好语言,qt可以用python和cpp啊,EPS32和mcu开发也可以实现cpp和python,linux的话,建议还是c开发,也可以使用rust,还可以使用luna,有需求就可以去学习。语言知识一个工具,只要能实现功能就好了。

  • 引导程序

  • 生产中特定flash读写序列号和特殊属性

  • 多层引导设计思路

  • api接口和sdk设计

  • 模块化编程思路

  • HAL、BSP、驱动等

  • 模块调用

  • 内外flash读取 和 特定地址读写

  • 扩容预备 空间预留

  • section数据 attribute对齐规则

  • 代码框架 (这个看公司的设计特点了)

  • 外留接口来实现对外的通信,增加兼容性

  • 技术栈 web、 后端 、app 、mcu 、mpu 、ui 、hmi、算法、运维、其他专业技术、代码和项目管理如gitee github、

  • 静态库和动态库 linux和win的

产品代码加密

要想办法做密钥之类的,东西,加密自己的程序,比如使用section这些来对重要函数进行加密,然后再写回flash,这个是一个很好的加密手段。

上位机

一般产品尤其是控制,遥控,定位的设备,一般都是出异常后需要用户自行矫正的,这个时候,上位机就很有存在的必要,它往往需要跨平台,需要满足多设备,多屏幕。本质就是数据收发而已,不同产品需要校准和设计的上位机是不同的。其实有的时候,会考虑,为什么不用上位机,为什么用上位机呢?我感觉是要分场景的,比如我这里是生产工厂,生产工厂需要电脑吗?自然是除了烧录代码可能需要,测试,也是装机之后才能测试,所以,不存在一边烧录一边测试的太少了。

如果我们是直接面向工厂的,此时,开发一个上位机要考虑的东西,有很多的,电脑本身其实是一个不稳定,环境差异大,不实用的生产平台。而且员工很多都不会用电脑,电脑也容易误触。所以,一个生产治具是最好的了,可以先把代码烧录到治具中,在治具中进行烧录其他设备的操作,这样员工只要操作治具就好了,一个治具是比电脑稳定很多的,而且也不存在环境差异问题。

做过linux开发就知道了,linux难就难在环境配置,系统配置上,真的天差地别,时不时就是我这里运行正常,你那边就跑不了的bug,非常痛苦,如果我直接寄一个硬件治具过来,作为烧录、测试、标定的设备,那么一下子我们就成功同步了,对于直面工厂,生产来说,这个确实是比上位机可靠好用的。上位机更适合代码烧录,固件更新,用户自定义和调节,不适合生产,也不利于生产,治具效率高,体积小,速度快,上位机一般都是一对一,这就比不上治具可以一下子插上去一堆。

上位机的优势:适合客户自定义操作,更新,微调,这个算是售后,一般都是一对一进行操作,也可以用于给用户进行检修等操作。

治具的优势:对接工厂,同步环境和标准,增加生产效率,降低员工要求,设备可靠,可以实现一对多的高效生产需求和测试需求。

统一标准

这个怎么理解呢?不单单是产品的规格,还有就是产品的标准尺度,就像卷尺一样,施工场地的单位长度都不一样呢。

统一标准,就是啥都是一样的,所以,很简单,只要所有车间和工厂都是用一样的标准,就可以做到规范和误差容许。

还有一种就是目标是标准,但是初始配置是不一样的。比如,电机此时速度是100,另一个是200,一样的最大加速度,但是我都要跑2000米,使用pid进行调制之后,就要定位,这个就是需要一个标准。而且两个因为初始速度不一样,所以最后pid调制停下来的位置是不一样的,不过误差可以接受就行了,此时就可以进行一个数据定位,只要电机复位+运行时间,就可以运行到2000米,但是如果你把100的电机速度得到的运行时间给到了200的电机速度的电机时间,就会导致,误差增加,此时,就是违反了统一标准的规则,他们应该都是对其统一标准才对。

看名字就是知道了—猫是液体[doge]。我们根据日常习惯也知道液体这些东西,它会自己去钻缝,会流动,接触面积更大,可以适应很多结构,把液体倒入圆锥体里面,它就变成圆锥体的形态啦。

文件IO和数据流
一切都是数据流
流式传输
流媒体
TCP是一种流式协议
流带来了什么
流的缺点?
总结

DMA

直接存储器访问,看这个名字就知道了,只要知道请求访问地址,访问地址,访问大小,访问模式,就可以实现地址之间的数据转移了。常见的SPI、IIC、USART等。配置好DMA、设置好地址、大小、模式,让CPU给DMA芯片发送这个命令,就会让DMA去干活了,中间cpu都不用管DMA,这就解放了CPU,去干那些耗时而且简单的访问操作。

DMA回调

DMA有错误回调,传输一半回调,传输完毕回调,所以我们只需要在回调函数中进行处理就好了,触发中断获得cpu使用权,开干。注意要使用判断DMA是否HAL_OK的状态判断函数之后,才能使用收发哦。
而且还有一点就是DMA不同那些外设一样,它本身是触发不了什么接收和发送的,SPI绑定DMA。比如SPI接收,是因为先触发了SPI的中断,因为CPU把DMA连接到一个存储器地址和SPI上了,所以才会触发SPI_DMA的接收完毕中断回调哦

DMA通道和DMA数据流
STM32F1系列(如STM32F103)

在STM32F1系列中,DMA有通道(Channel)的概念。每个DMA控制器有多个通道,每个通道可以配置为处理一个特定的外设数据流。每个通道有它自己的寄存器集合,用于配置源地址、目的地址、数据长度等。

STM32F4系列(如STM32F407)

在STM32F4系列中,DMA有数据流(Stream)的概念。每个DMA控制器有多个数据流,每个数据流可以配置为处理一个特定的外设数据流。每个数据流有它自己的寄存器集合,用于配置源地址、目的地址、数据长度等。此外,STM32F4系列的DMA控制器还有FIFO(先入先出)机制,用于优化数据传输。

使用cube的时候,就会发现要么没有通道要么没有数据流,两者只有一个。因为他们使用也非常相似。

用F04举例子:

数据流

每个DMA控制器有8个数据流,每个数据流都能够提供目标之间的单向传输链路

每个DMA控制器可以同时配置多个数据流,但在某一时刻只允许有一个数据流使用DMA控制器。当多个数据流同时请求时,由仲裁器决定哪一个数据流优先使用DMA控制器。

通道

  每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。


仲裁器
  仲裁器用于在多个数据流同时请求时,解决请求冲突的问题。在硬件上,数据流的编号越低,请求优先级越高,仲裁器优先响应编号低的数据流。

  为实现更灵活的配置,数据流还可以设置软件优先级,软件优先级分为以下4个级别:

  — 非常高优先级
  — 高优先级
  — 中优先级
  — 低优先级

硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流
原文链接:https://blog.csdn.net/weixin_44567318/article/details/114003967

总结

其实用cube配置之后,用起来差不多的,我估计这个时候也没有使用标准库了吧,后面那些H系列的,比如H745还是异构双核的。反正大家也都是对着demo进行模仿的,只要代码能用是不是标准库都是一样用的。

打印

学C语言第一个就是printf(“Hello World\n”);这个可以说基本就是所有程序员的开头了,若干年之后再次看到这句话,真的会有不同的感觉吧。回到正题,为什么是打印呢?因为在上古时代,那些机械和电脑非常非常打的时候,输出是没有屏幕的,但是人们想要一个记录和输出,此时就是打卡机了,就是在纸上打孔,这个孔就是数据输出啦,随着慢慢发展,也就沿用下来了。

打印操作

这个真的太多了,而且分平台,分语法,分架构,分系统等等,不过,现在是很少用打印机了,写代码还用打印机才是逆天啊。所以,一般都是屏幕直接现实或者命令行输出。

C语言就是fputc、printf等操作;linuxc就是printf、perror等操作;Qt就是qdebug咯;python就是print啦;自然还有mcu那些非windows,非控制台的环境了,他们就得重定向才能使用打印函数哦,就是把打印函数重写成串口发送等函数。这里要提一下啊了,为什么,嵌入式不能使用控制台。我当时学c语言的,都是devC++和gcc编译的,我觉得有控制台才对啊,为什么mcu就不能有控制台那些东西呢,还要费尽心思搞那些环境和芯片配置,后面懂得越来越多才知道,其实所有编译都可以算是交叉编译,因为都是为了满足一个平台和架构的特性,之所以在windows上面可以跑控制台是因为,他本来就是用自己的编译链,本来就是跑自己环境和架构,同理因为gcc是支持linux环境,所以linux才能用gcc编译,然后在命令行中打印出来,后面又要使用交叉编译才能烧录到特定的架构的芯片上,因为linux环境和架构是同目标的芯片又是不同的,所以还得要一次交叉编译才行。

花活,多颜色打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
一个朋友 11:24:59
#ifndef _COLOR_H_
#define _COLOR_H_

#define NONE "\e[0m" //清除颜色,即之后的打印为正常输出,之前的不受影响
#define BLACK "\e[0;30m" //深黑
#define L_BLACK "\e[1;30m" //亮黑,偏灰褐
#define RED "\e[0;31m" //深红,暗红
#define L_RED "\e[1;31m" //鲜红
#define GREEN "\e[0;32m" //深绿,暗绿
#define L_GREEN "\e[1;32m" //鲜绿
#define BROWN "\e[0;33m" //深黄,暗黄
#define YELLOW "\e[1;33m" //鲜黄
#define BLUE "\e[0;34m" //深蓝,暗蓝
#define L_BLUE "\e[1;34m" //亮蓝,偏白灰
#define PURPLE "\e[0;35m" //深粉,暗粉,偏暗紫
#define L_PURPLE "\e[1;35m" //亮粉,偏白灰
#define CYAN "\e[0;36m" //暗青色
#define L_CYAN "\e[1;36m" //鲜亮青色
#define GRAY "\e[0;37m" //灰色
#define WHITE "\e[1;37m" //白色,字体粗一点,比正常大,比bold小
#define BOLD "\e[1m" //白色,粗体
#define UNDERLINE "\e[4m" //下划线,白色,正常大小
#define BLINK "\e[5m" //闪烁,白色,正常大小
#define REVERSE "\e[7m" //反转,即字体背景为白色,字体为黑色
#define HIDE "\e[8m" //隐藏
#define CLEAR "\e[2J" //清除
#define CLRLINE "\r\e[K" //清除行

#endif

printf("This is a character control test!\n" );
printf("[%2u]" CLEAR "CLEAR\n" NONE, __LINE__);
printf("[%2u]" BLACK "BLACK " L_BLACK "L_BLACK\n" NONE, __LINE__);
printf("[%2u]" RED "RED " L_RED "L_RED\n" NONE, __LINE__);
printf("[%2u]" GREEN "GREEN " L_GREEN "L_GREEN\n" NONE, __LINE__);
printf("[%2u]" BROWN "BROWN " YELLOW "YELLOW\n" NONE, __LINE__);
printf("[%2u]" BLUE "BLUE " L_BLUE "L_BLUE\n" NONE, __LINE__);
printf("[%2u]" PURPLE "PURPLE " L_PURPLE "L_PURPLE\n" NONE, __LINE__);
printf("[%2u]" CYAN "CYAN " L_CYAN "L_CYAN\n" NONE, __LINE__);
printf("[%2u]" GRAY "GRAY " WHITE "WHITE\n" NONE, __LINE__);
printf("[%2u]" BOLD "BOLD\n" NONE, __LINE__);
printf("[%2u]" UNDERLINE "UNDERLINE\n" NONE, __LINE__);
printf("[%2u]" BLINK "BLINK\n" NONE, __LINE__);
printf("[%2u]" REVERSE "REVERSE\n" NONE, __LINE__);
printf("[%2u]" HIDE "HIDE\n" NONE, __LINE__);


C语言中使用printf()打印漂亮的颜色字体_c语言printf颜色-CSDN博客

这里巧妙的使用字符串的自动拼接,而且不只是改变字体颜色那么简单哦,还可以改背景色,前景色等诸多操作,感兴趣去了解一下。给自己的枯燥无味的debug生活一点乐子咯。还有那种ai雌小鬼助手,打印出来的那些杂鱼啥的特殊颜色字体应该都是这样可以实现的哦。

日志

一开始,我看到那些神秘兮兮的log函数,我真的觉得有什么特别牛的东西,后面我才知道,他就是打印的一层封装,他本质还是打印函数,不过是会打印出操作、警告、操作者等属性,就是打印之前还会把其他属性也打印出来。这个在mcu中,因为空间的问题,所以都是直接打印出来没有保存的,如果使用文件系统就可以存放到文件系统,这样可以命令行去访问特定文件来知道干了什么,前提是空间要够啊。linux就不用说了,你敲的每一行指令,都有log历史记录的,用户是谁,干了什么,是否危险,什么权限都知道的。

内核打印

这个就是搞linux内核驱动的啦。Linux 内核共提供了八种不同的消息级别,分为级别 0~7。数值越大,表示级别越低,对应的消息越不重要。相应的宏定义include/linux/kern_levels.h 文件中。

KERN_EMERG 表示紧急事件,一般是系统崩溃之前提示的消息;
KERN_ALERT 表示必须立即采取行动的消息;
KERN_CRIT 表示临界状态,通常涉及严重的硬件或软件操作失败;
KERN_ERR 用于报告错误状态,设备驱动程序会经常使用该级别来报告来自硬件的问题;
KERN_WARNING 对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题;
KERN_NOTICE 表示有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报;
KERN_INFO 表示内核提示信息,很多驱动程序在启动的时候,用这个级别打印出它们找到的硬件信息;
KERN_DEBUG 用于调试信息。

1
2
3
4
5
6
7
8
#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */

注意:打印消息的级别大于控制台的打印级别时,才能出现在终端。并且:数字越大,级别越低

1
2
$ cat /proc/sys/kernel/printk
4 4 1 7

控制台打印级别 消息的默认打印级别 控制台最大打印级别 控制台最小级别

四个数值的含义如下:

  • 控制台日志级别:优先级高于该值的消息将被打印至控制台;
  • 默认的消息日志级别:将用该优先级来打印没有优先级的消息(即 printk 没有指定消息级别);
  • 最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);
  • 默认的控制台日志级别:控制台日志级别的缺省值。
1
2
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);   //此时是没有设置打印等级的,就是默认4
printk(KERN_DEBUG "HELLO LINUX PRINTK\n"); //这个就是设置等级啦,KERN_DEBUG

Linux内核之 printk 打印_linux printk-CSDN博客

总结

打印是入门和快速测试的基础,日志是可以记录所有操作,可以保存到本地,这样其实是更加有利于bug复现的,还有调试交互,后续设计的,不过mcu和那些空间本来就少的设备是真的遭不住的。内核打印,就是写驱动的人才用得到的东西,因为此时打印函数都是无效的了,只能通过内核提供的打印函数才能实现打印效果哦。

定时器

MCU的经典外设之一了,这个确实有点新手噩梦的感觉了,尤其是当时学时钟树,我愣是横看竖看没看懂,现在是看懂也学会了,但是,我搞混了一个概念,也就是三大时基中的自动重装载了。

三大时基

预分频,计数器,自动重装载这三个就是大名鼎鼎的三大时基,可以说,只有配好他们才能保证定时器是稳稳当当的。此时就要看时钟树给此时定时器分了多少的频率,此时再通过我们的预分频就能设置为多少hz为一个节拍,计数器呢就是多少个节拍算完成任务,自动重装载我一般都是使能的,因为我一直以为是cnt到了计数器的时候,是自动重装载把cnt归位的,没想到还是计数器归位的。为什么我发现这个问题呢?直到有一天我发现没配置也能实现中断触发和回调的时候,我的世界观崩了!

自动重装载和影子寄存器
TIM触发一次
TIM中断
TIM的PWM输出
TIM相位编码
TIM刹车
总结

起因

我写好了协议层和应用层,但是我的同事也写了一个bootloader,但是他…所以,在我和他多次交流和协商下,他搞定了bootloader,但是出现问题了,就是adc不一样,他给的adc表格,不符合我实际读取的,所以就会出现偏差。

bug查找

我一开始以为是采样问题,看了周期,看了转换速度,看了分频,都是正常,代码cube一键生成,也是启动adc了,因为使用的F1的MCU,所以是不能修改分辨率的,统一是12,F4的那些高级一点才能自己选择分辨率哦。

最后,我让他把源码发给我了,我看了中断回调,看了函数处理,看了数据处理,都没问题,最后看到了,他没有校准ADC,这个我是早早的写了,我启动ADC就立刻校准了。

为什么要校准

我还记得高中和初中的时候,我还是物理科代表呢,但是做啥实验第一步都是清零校准,才能保证统一和实验性。放在我们现在的工作之中就是,使用万用表和示波器肯定要先校验才能保证数据和波形是正确的啊。所以,在处理传感器和adc,dac等方面都要注意校验的问题,不是说光初始init就能解决一切bug的。

那为什么会出现误差呢?

STM32 的 ADC 校准一般有 参考电压校准 和 增益校准。

参考电压校准:

先测量 ADC 参考电压的实际值,然后将该值与预设的参考电压进行比较,得到参考电压的偏差,最终通过校准将其校正的方式叫做 参考电压校准 ,其目的是为了准确测量 ADC 的输入信号。

叫做增益校准:

通过测量内部基准电压和 ADC 输入信号的幅值之间的比例关系,校准 ADC 增益的方式 叫做增益校准,其目的是确保 ADC 输出的数值与输入信号的幅值之间具有良好的线性关系,为了准确的转换 ADC 输入信号。

ADC 校准的目的是为了消除 ADC 的偏移误差和增益误差,从而提高测量精度。
原文链接:https://blog.csdn.net/weixin_42328389/article/details/129518720

这个就像芯片生产过程制造中的差异化一样,ADC部分也存在一些差异化(虽然很小),其参考电压、偏置电压、增益等参数可能存在一些不确定性和漂移,这些参数的变化会导致 ADC 的测量结果产生误差。

就想上面介绍什么是 ADC 校准最后说的,为了提高测量精度,消除 ADC 的偏移误差和增益误差,所以在使用 ADC 采样的时候都需要进行 ADC 校准。

只要不改变adc设置就不会变化哦,修改通道是没事的,前提是别改属性,也就是可以关闭,但是寄存器别动,下次继续使用是没问题的。如果工作环境一直发生变化,噪声环境比较恶劣,温度啊,干扰,那就得用一次校准一次了。

解决办法

就是启动ADC就立刻使用校准咯,这样就可以保证不同设备,或者同一个设备不同程序一样的adc啦。

起因

还是经典的摸鱼?不是的,是我公司的一个工程师,让我帮他搞一下治具,突然发现,烧不了代码了,所以,我经过j-flash的测试,才知道加读保护了。而且用的还是APM32这个芯片,我没想到是APM32是直接照抄STM32的,甚至我的STM32的bin代码都可以直接跑哦,真合着寄存器,外设引脚,内部完全一模一样呗。所以,我想到做一期关于flash的博客。来巩固一下知识点。

FLASH是什么

FLASH就是一种掉电不丢失的储存单元,特性就是成本低,但是不能按位操作,空间大,结构简单。

为什么,不能按位呢?因为FLASH本身就是一个半导体一样的东西,我们看到刷新flash全是置为FF,为什么是FF,是因为此时单元束缚了一个电荷单元,旁边的高阻抗的传感器识别到,这个位置有电势差,所以就知道这个位bit里面有数据的。此时,就是把电子拿出来简单,放回去就难了,所以需要刷新才能重写。为什么拿出来和放回去难度不一样呢?因为上下结构不一样的。

上面是一个flash单元,因为flash下面的所有单元衬底其实是一整块的,所以,无法做到,按位进行操作,因为一操作就会导致所有数据都归零,所以,只能操作上面部分的电荷来实现放电操作。刷新过程,其实就是衬底清空,此时就会对上面的浮栅进行充电,然后,电压一走电荷就会被束缚在栅极之中。

还有那些颗粒类型什么的,我就不太了解了,因为我是一个臭写代码的,这个FLASH设计,要专业人士才能操作。当前存储颗粒主要分类:SLC、MLC、TLC、QLC

可以认为那些储存都是这些FLASH颗粒组成的哦。上面这个只是一个二维的设计,因为这个衬底可以设计成3维的,所以,3维的flash成本更低,同时擦除的块也变大了,这个是厂商设计的哦。

为什么设计简单,我们可以举例子,比如eemprom,这个其实就是内部类似矩阵一样,可以知道横纵就知道位置和操作了,因为没有采用统一衬底的设计,所以可以对任意位置操作,带来的布线、设计成本、生产成本,容量大小等诸多问题,都是限制它的原因。

还有就是速率啦,FLASH一般是SRAM或者SPI的,eemprom一般都是iic

FLASH类型

目前市面上常见的集成存储芯片类型:eMMC、UFS、SPI-Flash、QSPI-Flash、各尺寸的SD卡等。

我这里就分为2大类:第一个是NORFlash,第二个是NANOFlash

怎么理解呢?

NORFlash就是扩容的一个Flash,我们可以修改MCU的内存大小和BANK来使用外部的SRAM或者NORFlash,在外部上面跑代码就是指针直接指向,把外部的也当成SRAM跑,而且还是很可靠的,掉电不丢失,突发情况说不定还能查看内部bit来还原数据呢。读速度还行,写速度慢,每次使用都要刷新,擦写次数比NANOFlash少太多了。它的接口是SRAM的接口,同MCU的引脚连接。

NANOFlash就是纯纯的存储空间啦,一般都是有一个FLash芯片来实现对这写Flash单元的控制的,此时一般都是SPI,也有很多使用QSPI,一般是好的FLASH芯片都是有QSPI支持的,然后希望手册多一些有用的指令,便宜的FLash确实可以用,但是它的功能和指令都被阉割了。如果想要实现QSPI和文件系统等一些操作就比较麻烦了。读写速率都很快,但是不可靠,寿命高,可以擦写很多很多次。

内部FLASH

回到上面提到的读保护吧,确实一般在mcu中,只有写保护,就是想要修改FLASH,需要解除锁,才能刷新再写,读的话,一般是直接地址指过去了。那么读保护是干嘛的?我这内部完全没有限制我读取啊?

读保护是防外人的,那些狗东西最喜欢逆向和偷别人代码了,此时我开启读保护等级1,他们就不能通过调试来获取我的代码了。但是此时MCU是可以恢复到读保护等级0,只需要选择使用boot0 boot1都为1的SRAM启动,我们修改运行地址到SRAM中,修改映射,就可以在SRAM中把读保护等级修改了。但是如果读保护等级为2的时候,就把SRAM也禁用了,此时就gg了。

此时,不能更新了吗?不是的,我们此时是禁用了外部的操作啊,但是我们可以ota啊,我们程序还是可以通过内部的开锁关锁来实现对flash的更新,这个是内部的操作,不是来自外部的哦[doge]

SRAM FLASH ROM区别

SRAM和NORFlash都可以运行代码,他们的区别就是SRAM肯定更快啊,想都不用想的,而且不用担心丢失,掉电就没了,固态使用的DRAM,还需要定期充电呢维持呢,速度也快,成本贼低。

ROM和NANOFlash的区别呢?都想是存储的东西,但是,本质还是不一样的。ROM是一种只读的统称,可以认为是PROM(可编程ROM)、EPROM(可擦除可编程ROM,通过紫外线擦除)、EEPROM。ROM也是可以编程的,但是更少,Flash就擦除写入的多了,一般的固态SSD都是FLASH,因为便宜快速高效可以反复擦除。

感想

FLASH真的一个跨时代的发明。我想到了,最近要搞一个NAS玩玩,先玩黑群晖吧,刚好有一个闲置的垃圾笔记本,我看了一下群晖裸机也要1300呢,有点贵的,用的是机械盘,本质,是中间那个磁盘来着,怎么说呢。那个东西可以修复确实好,不过固态确实越来越好用。服务器还是机械吧,固态坏了真的修不了,都是电子和位,怎么恢复啊。FLASH带来的便利,也带来一些麻烦,比如固态涨价涨疯了,我都买不起了,呜呜呜。

起因

我朋友发了一个sqi的flash芯片手册给我看了一下,那个是一个垃圾的国产芯片,只有简单的读取和写入功能,很多其他指令都没有,他说很难实现sfud的功能,sfud就是为了兼容不同型号的flash,我感觉确实挺不错的,有水平的,新出的flash一般都是支持SFDP标准的,而sfud也是支持的SFDP标准的,所以,只要满足就可以使用啦。想都想到flash就一个FLASH博客咯。

NANOFlash底层驱动

这个就是纯纯数据啦,现在固态都是NANOFLash的哦,(服务器的还是用机械盘,因为机械盘坏了可以修复,固态坏了是真的没救了)。是不能直接执行代码的,不过可以通过引导程序把代码下载到ram之中来运行哦。接口一般是看芯片的设计的,芯片有spi、m.2 、pcie、sata都是可以的,通信协议只是一个建立交互的手段罢了,速率和成本满足要求,怎么样都行。这个就没啥好操作的了,就是根据协议,写好收发,看着手册进行指令的发送,状态的判断,数据的获取和写入。封装成一个个api给上层调用咯。

NORFlash底层扩容操作

NORFlash一般不是用来存储东西的,更多是扩容和进行代码运行的,它是跟mcu的扩容引脚接在一起的,也就是说,mcu可以配置外部的bank之后,可以直接访问这个外部的Flash地址来进行操作,而且这个是非常可靠的,缺点肯定是有的,比如SRAM快和高效,掉电不丢失,重启要刷新等。

使用方法,一般都是抽象使用,也就是mmu来管理这块地址,因为flash刷新就会按照块来进行处理的,所以,里面最好不要又可变的变量,而是一个固定不变的数据和函数,当然可以用指针去直接使用它,没有mmu的管制,想干嘛都是可以的。实践中现代操作系统和环境通常会提供更高级别的抽象来管理内存和代码的执行,而不是直接由程序员操作指针去执行Flash中的代码。不过,在嵌入式系统或特定的低级编程场景中,这种直接操作更为常见。

QSPI

我们使用的SPI,一般都是sck,miso,mosi,css组成的,可以放弃一根线,只有mosi或者miso,也有使用qspi的,就是并行spi的,可以认为这种spi就是利用并发的操作来增加速率,每多一根并行,理论上是可以翻倍速率的,具体还是看QSPI硬件芯片的处理了。

文件管理系统

这个就是FLASH的底层调用啦,通过指令,知道空间大小,可以通过指令去处理和读写数据、文件等操作。

文件管理系统是操作系统的一个关键组成部分,负责组织、存储、检索用户和系统数据。它为用户提供了一个访问和管理文件的接口,使得用户能够创建、删除、修改、重命名文件以及目录(文件夹)。常见的文件管理系统有NTFS(Windows)、HFS+(macOS早期版本)、APFS(macOS新版本)、EXT4(Linux)等。它们通过各种数据结构(如inode、文件分配表等)来跟踪文件存储的位置、大小、权限等信息。

FLASH,通常指的是闪存技术,是一种非易失性存储器,意味着断电后存储的数据不会丢失。与传统的硬盘驱动器(HDD)使用磁性介质不同,闪存使用电子存储数据,在读写速度、功耗、抗震性方面具有优势。FLASH技术分为两类:NAND Flash和NOR Flash,其中NAND Flash因其高密度、低成本和较高速度的特点,广泛应用于USB闪存盘、固态硬盘(SSD)、移动设备的内部存储等场景。

虽然文件管理系统和FLASH技术分属不同层面,但它们紧密相关。现代的存储设备,特别是固态硬盘(SSD),大量采用FLASH技术作为存储介质。为了优化FLASH存储的性能和寿命(因为FLASH有擦写次数限制),文件管理系统需要针对FLASH特性进行特别设计或调整,比如引入TRIM指令来通知SSD哪些数据块已不再使用,可以提前擦除以提高写入效率;或者使用磨损均衡算法来均衡FLASH芯片中各区块的擦写次数,延长SSD寿命。

  1. 理解FLASH硬件特性:首先,深入了解您所使用的FLASH(NAND或NOR)的技术规格、命令集、读写速度、擦写寿命限制等,这对于设计高效且安全的接口至关重要。
  2. 开发驱动程序:对于嵌入式系统或特定应用,可能需要编写低级驱动程序来直接控制FLASH芯片。这包括初始化FLASH、发送读写命令、处理错误和状态返回等。
  3. 设计高级API:在驱动程序之上,设计一套易于使用的API,比如打开、关闭、读取、写入、 seek(定位)、truncate(截断)等操作,模仿文件I/O的方式,这样上层应用可以不关心底层硬件细节。
  4. 错误处理与磨损平衡:考虑到FLASH的擦写寿命,您的API设计应包含磨损平衡策略和错误处理机制,比如记录和报告坏块信息,使用损耗均衡算法等。
  5. 性能优化:根据FLASH的特性(如页大小、块大小)优化读写操作,比如批量读写以减少开销,利用缓存技术加速访问等。
  6. 测试与验证:彻底测试您的接口在各种条件下的表现,确保数据的完整性和可靠性。

现代文件系统通过维护元数据来跟踪文件的分配情况,通过多种机制确保数据一致性和空间的有效管理,同时提供给上层应用接口以实现并发访问控制和空间的精确计算。

其实就是一句话,文件系统给了需要接口,我们就得从底层提供api比如多大,读写操作api,空间使用等。然后文件系统就会自己去处理和判断空间剩余,空间使用,然后再封装api就可以进行文件io的操作了。

虚拟文件系统(Virtual File System, VFS)

现代操作系统内核中的一个重要组件,它提供了一个抽象层,使得操作系统和上层应用程序可以以统一的方式访问不同类型的文件系统,而无需关注底层文件系统的具体实现细节。VFS在处理并发访问和空间管理时,也遵循类似的原理,但其作用范围和方式更为通用和抽象。

并发访问控制

在VFS层面处理并发访问,主要是通过文件描述符、文件句柄以及与之相关的锁机制来实现的。当多个进程或线程试图访问同一个文件时,VFS会确保通过以下方式避免冲突:

  • 文件描述符表:每个进程都有一个文件描述符表,其中包含了指向内核中打开文件结构的指针。这些结构包含了锁和其他同步机制,以保护文件的并发访问。
  • 锁机制:VFS支持不同类型的锁,如读锁和写锁,来控制文件的访问权限。这些锁可以是进程间共享的,也可以是独占的,确保了读取和写入操作的原子性和一致性。
  • 同步原语:操作系统提供的同步原语(如信号量、互斥锁等)同样可用于VFS层面的同步控制,尤其是在涉及更复杂的并发控制策略时。

空间管理

VFS不仅管理文件的逻辑表示,还间接参与空间的管理,尽管具体的磁盘块分配和空闲空间跟踪通常由下层的文件系统实现:

  • 元数据抽象:VFS定义了统一的元数据接口,允许上层查询文件大小、链接数等信息,而底层文件系统负责实际的物理空间分配和释放。
  • 空间分配策略:虽然具体的块分配算法(如位图、B树等)由具体文件系统实现,VFS确保了上层应用可以通过标准接口请求和释放空间,而不必关心底层细节。
  • 跨文件系统操作:VFS使得不同文件系统可以挂载在同一目录树下,每个文件系统独立管理自己的空间,VFS提供了一个统一的视角,让上层可以透明地访问和管理这些不同文件系统中的文件。

总之,虚拟文件系统通过提供统一的接口和抽象,简化了对文件系统多样性的管理,同时也为并发访问控制和空间管理提供了一个基础框架,确保了操作的高效和一致性。

FLASH应用层

这个应用层是给到程序员的啊,一般都是在文件系统之上使用文件io去操作和处理很多事情,增删查改;还有使用数据库、日志等操作,这些操作都是看起来简答的api调用,实际上是底层驱动和文件系统做了很多处理的。写好应用层就可以给用户使用啦,这里要多注意判断的判断、纠错、校验、以及用户体验等问题。

FLAS用户层

就是用户看到的数据啦,我们使用文件管理系统就可以知道当前的状态了,此时可以使用上位机的办法或者指令的办法去固态进行处理和操作,其实就是对文件系统的那些io操作进行处理而已。比如打开我的电脑,就可以直接看到固态空间和使用情况,还可以直接进行修改,这个就是一个非常简单的使用了,已经是高度封装和抽象了。

总结

FLASH的原理和使用是简单的,如果直接为了实现通信和简单交互,其实是很简单的,但是现实是,用户是要体验的,要速率的,要稳定性,要可靠安全高效的,这个时候就要文件系统做非常大的校验和判断,还有底层的代码要不断优化,增加校验,增加纠错,增加速率,甚至修改通信协议等诸多问题,来给用户带来超棒的体验。