0%

遇到问题

今天用STM32CubeMX生成了代码,使用printf函数后单片机程序运行异常,查出是STM32CubeMX生成的代码没有printf的重定向,百度查了两个方法后两个都行不通,最后参考正点原子例程解决。

参考

  • 错误参考:一知半解学CubeMX——UART:Printf实现

  • 正确参考:正点原子例程SYSTEM文件夹下的usart.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //加入以下代码,支持printf函数,而不需要选择use MicroLIB
    #if 1
    #pragma import(__use_no_semihosting)
    //标准库需要的支持函数
    struct __FILE
    {
    int handle;
    };

    FILE __stdout;
    //定义_sys_exit()以避免使用半主机模式
    void _sys_exit(int x)
    {
    x = x;
    }
    //重定义fputc函数
    int fputc(int ch, FILE *f)
    {
    while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
    USART1->DR = (u8) ch;
    return ch;
    }
    #endif

注意事项

  1. 要注意重定向的函数名是int __io_putchar(int ch)还是int fputc(int ch, FILE *f)
1
2
3
4
5
6
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
  1. CubeMX生成的代码没有勾选Use MicroLib,可能需要勾选,可以看Keil中的USE MicroLib说明,MicroLib不支持操作系统函数。

  2. Unix系统里,每行结尾只有“<换行>”,即”\n”;

    Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;

    Mac系统里,每行结尾是“<回车>”,即”\r”;

    参考:\r,\n,\r\n的区别:回车与换行

书籍介绍

作者: [美] Eric Bogatin
出版社: 电子工业出版社
原作名: Signal and Power Integrity:Simplified, Third Edition
译者: 李玉山 / 刘洋 / 初秀琴 / 路建民

第一版前言

“一切都应该尽可能简单,而不只是简单一点。” ——阿尔伯特·爱因斯坦

信号完整性问题的10个基本原则

  1. 提高高速产品设计的关键是:充分利用分析工具实现准确的性能预估,使用测量手段验证设计过程,降低风险并提高所采用设计工具的可信度。

  2. 将问题的实质与表面现象剥离开的唯一可行途径是:采用经验法则,解析近似,数值仿真工具或测量工具获得数据,这是工程实践的本质要素。

  3. 任何一段互联,无论线长和形状,也无论信号的上升边如何,都是一个由信号路径和返回路径构成的传输线。一个信号在沿着互联前进的每一步,都会感受到一个瞬时阻抗。如果阻抗恒定为常数,比如具有均匀横截面传输线的情况,那么其信号质量将会获得奇迹般的改善。

  4. 把“接地”这个术语忘掉,由于它所造成的问题比用它解决问题还要多。每一路都有返回路径。抓住“返回路径”,像对待信号路径一样去寻找并处理返回路径,这样有助于培养解决问题的直觉能力。

  5. 当电压变化时,电容器上就有电流流动。对于快速变化的前沿,即使印制电路板边沿和悬空导线之间的空气间隙形成的边缘线电容,都有可能拥有较低的阻抗。

  6. 电感与阻扰电流周围的磁力线匝数有本质的联系。只要电流与磁力线的匝数发生改变,在导线的两端就会产生电压。这一电压是导致反射噪声,串扰,开关噪声,地弹,轨道塌陷及电磁干扰的根源之一。

  7. 当流经地回路电感上的电流发生变化时,在接地回路导线上产生的电压称为是地弹。它是造成开关噪声和电磁干扰的内在机理。

  8. 以同频率的方法作为参照,信号带宽是指的有效正弦波分量的最高频率值。互联模型的带宽是指在这个最高的正弦频率上,模型仍然准确的预估互联的实际性能。在使用模型进行分析时,一定不要让信号的带宽超过模型的带宽。

  9. 除了少数情况之外,信号完整性公式中给出的是定义或者近似。在特别需要准确的场合就不使用近似。

  10. 有损传输线引起的问题是上升边退化。由于集肤深度和介质损耗,损耗随着频率的升高而增加。如果损耗随着频率的升高而保持不变,上升边就不会发生变化,这时的有损线只是增添了一些不便而已。

100条使信号完整性问题最小化的通用设计规则

100条估计信号完整性效应的经验法则

实验环境

实现功能

  • 实时频率测量
  • 测量范围1Hz-50Mhz
  • 测量误差0.0005%@20MHz
  • uart串口打印

不足思考

  • 可以使用PLL提高sys_clk,使测量范围增到100MHz
  • 程序逻辑复杂,还需简化
  • 自适应闸门时间,在高频的时候提高实时测量的频率,低频的时候又可以准确测量,可使测量范围下限更小

Github发布

参考视频

思考与心得

  1. 本书结构为:编码风格、系统移植系统配置、任务函数、中断配置、临界区保护、多任务、列表结构、系统任务调度器、任务切换、时间管理、队列结构、信号量、软件定时器、事件标志组、任务通知、Tickless模式、空闲任务、内存管理

  2. 跟今年2月份学的rtThread差不多,rtThread是参考官方文档学习的,学习了内核的一部分,和本次学习《FreeRTOS源码详解与应用开发》的内容差不多,也是借本书回忆一下之前学的实时系统。比较关心对接口的使用,本书的源码详解有一部分没有细看,日后需要的时候再啃源码,也可以自己手写一个实时系统来理解内核中核心的部分。

  3. 有些公司可能不想用实时系统,理由是影响速度,那么问题来了,什么情况下需要采用嵌入式操作系统呢?看过这个知乎的回答就知道,大型项目中裸奔会造成资源的浪费,特别是处理GUI、lwip、fatfs等,里面有大量的delay。在需要并行这些就需要用到rtos,使用os还可以实现应用层与底层硬件的隔离,可以方便分块开发、移植等。另外在资源数较少的mcu上不很适合使用rtos。

  4. 光看书没有用,我欠缺rtos的实践,最好是拿个项目练练手。

210710-第一块四层板-1.jpg

  • 第一次画四层板,嘉立创打板的时候出了点问题,嘉立创PCB解析和AD21.3.1版本的不兼容,导致内层存在空气间距

  • 我还测试了下这个TVS和PTC防反接啥的,效果都蛮不错的

  • 总体来说,感觉这块板子设计的蛮不错,目前没啥别的问题

参考

GND可以分为几种

  1. 模拟地线AGND
    模拟信号是微弱信号,容易受到其他电路大电流的影响,大电流会在模拟电路中产生大的压降,会使得模拟信号失真。

  2. 数字地线DGND
    有按键检测电路、USB通信电路、单片机电路,在由0跳变到1的过程,电压产生了变化,根据麦克斯韦电磁理论,变化的电流周围会产生磁场,也就会形成EMC辐射,使用单独的DGND与其他电路隔离,防止辐射扩散。

  3. 功率地线PGND
    大电流会造成不同功能电路之间的地偏移现象。

  4. 电源地线GND
    是所有电路的0V电压参考点,是电源的地线GND。

  5. 交流地线CGND
    在AC-DC电源电路中,一个是交流地线,一个是直流地线,交流地线作为交流电路部分的0V参考点,直流地线作为直流电路部分的0V参考点。通常为了在电路中统一一个地线GND,工程师会将交流地线通过一个耦合电容或者电感与直流地线连接在一起。

  6. 大地地线EGND
    地线是在电系统或电子设备中,接大地、接外壳或接参考电位为零的导线。为了增强电路的安全系数,工程师一般在高压大电流的项目中使用大地的地线EGND,例如在家用电器电风扇、电冰箱、电视机等电路中。

不细分GND会导致的问题

  1. 信号串扰
    假如将不同功能的地线GND直接连接在一起,大功率电路通过地线GND,会影响小功率电路的0V参考点GND,这样就产生了不同电路信号之间的串扰。

  2. 信号精度
    交流电源的地线CGND由于是正弦波,是周期性的上下波动变化,它的电压也是上下波动,不是像直流地线GND一样始终维持在一个0V上不变。将不同电路的地线GND连接在一起,周期性变化的交流地线CGND会带动模拟电路的地线AGND变化,这样就影响了模拟信号的电压精度值了。

  3. EMC实验
    信号越弱,对外的电磁辐射EMC也就越弱;信号越强,对外的电磁辐射EMC也就越强。假如将不同电路的地线GND连接在一起,信号强电路的地线GND,直接干扰了信号弱电路的地线GND。

  4. 电路可靠性
    电路系统之间,信号连接的部分越少,电路独立运行的能力越强;信号连接的部分越多,电路独立运行的能力就越弱。

GND和机壳的连接

​ PCB板卡置于金属机壳中,机壳一般接大地,PCB的GND与机壳之间经常使用一个电容(1nF/1KV)并联一个电阻(1M)连接。

  1. 电容是干啥用的
    从EMS(电磁抗扰度)角度说,这个电容是在假设PE良好连接大地的前提下,降低可能存在的,以大地电平为参考的高频干扰型号对电路的影响,是为了抑制电路和干扰源之间瞬态共模压差的。其实GND直连PE是最好的,但是,直连可能不可操作或者不安全,例如,220V交流电过整流桥之后产生的GND是不可以连接PE的,所以就弄个低频过不去,高频能过去的路径。从EMI(电磁干扰)角度说,如果有与PE相连的金属外壳,有这个高频路径,也能够避免高频信号辐射出来。
  2. 一般在1nF左右比较合适
    如果答主在变频器、伺服驱动器这样8~16kHz开关频率的工业设备上用这么大的电容值,那么,用户摸外壳会有触电的风险的。一般选到这么大,都是电路其他地方设计不合格,为了对付EMC测试,只好把这个电容加大的。最好是安规电容,GND和PE间选用Y电容
  3. 1M电阻是干啥用的
    这是对付ESD(静电放电)测试用的。因为这种用电容连接PE和GND的系统(浮地系统),在做ESD测试的时候,打入被测电路的电荷无处释放,会逐渐累积,抬升或降低GND相对与PE的电平,累积到一定程度,超过了PE和电路之间的绝缘最薄弱处所能耐受的电压范围,GND和PE之间就会放电,几个纳秒间,在PCB上的产生数十到数百安培的电流,这足以让任何电路因EMP(电磁脉冲)宕机,或者是让PE与电路之间绝缘最薄弱处所在信号连接的器件损坏。但是刚才说了,有时候又不能直接连接PE和GND,那么就用一个1-2M的电阻去慢慢释放这个电荷,以消除二者间的压差。当然1-2M这个数值是根据ESD测试标准选择的,因为IEC61000里面规定最高的重复次数只有10次/秒,如果你搞个1000次/秒的非标ESD放电,那么1~2M的电阻我觉得是不能释放掉累积的电荷的。1M电阻的高阻接地方式,往往是为了在提供EMC保护的同时或者说不影响防护效果的同时限制故障电流,印象中1M是根据人体模型得到的结果。比如内部挂了,GND连到高压上的故障,有这个1M,通过的电流不会伤害人体。

  4. PE不可靠!因为很多国内的客户根本不会给你接上有效的PE,也就是说,你根本无法依靠PE来提升EMS或降低EMI的指标。其实这也不能全怪客户,是因为他们的车间、厂房、办公室根本就没按照电工标准来修,压根就是没有接地线的!所以,我明白PE不可靠以后,就使用一些技巧让电路能够硬抗过EMS测试。

  5. PE(机壳)和GND直连是不行的。多见的系统是浮地,机壳连PE,PCBA不连PE,这样机壳就是个很好的法拉第笼,有效屏蔽外界。

环境

  • FPGA采用的是高云小蜜蜂家族的GW1N-LV4QN88C6/I5
  • 摄像头使用OV2640,可以配置数据为JPEG压缩后输出
  • 采用DC-DC降压到3.6V再LDO降至3.3V与2.8V,OV内核和FPGA内核采用DC-DC降到1.2V使用

FPGA部分

编程流程图

  1. I2C驱动
  2. FIFO配置
  3. 通过I2C配置OV2640寄存器
  4. 获取DCMI接口数据
  5. JPEG找出帧头帧尾
  6. 把有效数据存入FIFO
  7. SPI读FIFO数据输出

I2C驱动

参考:正点原子OV5640驱动程序

FIFO配置

通过FIFO SC HS IP核向导生成(SC是同步的意思),在使用的时候需要查看IP核用户指南,重点要看时序部分

SPI驱动

参考:SPI总线的原理与Verilog实现

经验总结

本次FPGA实现读取OV2640并SPI发出来的小功能,解决了低端MCU没有DCMI外设,遇到了IP核不会使用的问题,要多看手册里的时序部分。还有就是有些数据处理的时候要缓冲,加延时,否则会造成信号的错位。后面我要加强流水线结构的设计,多用并行的思想发挥FPGA的优势。

项目GitHub链接

https://github.com/hao0527/fpga-ov2640_fifo_spi

参考

SRAM与DRAM的区别

​ SRAM(Static RAM)与DRAM(Dynamic RAM),从名字上看,SRAM与DRAM的区别只在于一个是静态一个是动态。由于SRAM不需要刷新电路就能够保存数据,所以具有静止存取数据的作用。而DRAM则需要不停地刷新电路,否则内部的数据将会消失。而且不停刷新电路的功耗是很高的,在我们的PC待机时消耗的电量有很大一部分都来自于对内存的刷新。那么为什么我们不用SRAM来作为内存呢?

SRAM的基本单元结构图DRAM的基本单元结构图

210612-RAM-1.jpg

​ SRAM存储一位需要花6个晶体管,而DRAM只需要花一个电容和一个晶体管。cache(高速缓冲存储器)追求的是速度所以选择SRAM,而内存则追求容量所以选择能够在相同空间中存放更多内容并且造价相对低廉的DRAM。

​ 我们姑且不去讨论关于SRAM是如何静态存储数据(触发器)的。为什么DRAM需要不断刷新呢?

​ DRAM的数据实际上是存在电容里的。而电容放久了,内部的电荷就会越来越少,对外就形成不了电位的变化。而且当对DRAM进行读操作的时候需要将电容与外界形成回路,通过检查是否有电荷流进或流出来判断该bit是1还是0。所以无论怎样,在读操作中我们都破坏了原来的数据。所以在读操作结束后需要将数据写回DRAM中。在整个读或者写操作的周期中,计算机都会进行DRAM的刷新,通常是刷新的周期是4ms-64ms。

​ 关于SRAM和DRAM的寻址方式也有所不同。虽然通常我们都认为内存像一个长长的数组呈一维排列,但实际上内存是以一个二维数组的形式排列的,每个单元都有其行地址和列地址,当然cache也一样。而这两者的不同在于对于容量较小的SRAM,我们可以将行地址和列地址一次性传入到SRAM中,而如果我们对DRAM也这样做的话,则需要很多很多根地址线(容量越大,地址越长,地址位数越多)。所以我们选择分别传送行地址和列地址到DRAM中。先选中一整行,然后将整行数据存到一个锁存器中,等待列地址的传送然后选中所需要的数据。这也是为什么SRAM比DRAM快的原因之一。

多线程

什么是多线程

所谓多线程,就是系统可以同时运行多个任务,在操作系统中,每个任务就是一个线程。

Python 多线程可以成倍提高程序的运行速度。

进程是资源分配的最小单位,线程是程序执行的最小单位。

python3多线程

参考:Python3 多线程

异步

什么是异步

同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行。

异步是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果。

python3异步

参考:asyncio —- 异步 I/O

  1. (判断题)指针就是地址,因此一个变量的指针就是该变量的地址。
    答案:错误;解释:指针是个变量,指针的值是个地址,地址是个常量。

  2. char* s=”AAA”;
    s[0]=’B’; //错误,初始化指针时所创建的字符串常量被定义为只读,修改违法

  3. char ch;int i;float f;double d;
    表达式:ch/i+(f*d-i)的结果类型为double

  4. 1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
    int a = 5, b = 0;
    int c = MAX(++a, b);
    int d = MAX(++a, b + 10);
    printf("%d %d %d %d\n", a, b, c, d);
    return 0;
    }

    Output: 8 0 7 10
    解释:注意define,第一次调用MAX时,++a先执行了一次,此时a为6,由于满足宏定义中(a)>(b)的条件,所以执行a,这个a就对应++a,所以a又自增了一次,变为7,由此得出c为7。 第二个MAX时a又自增了一次,此时为8,由于不满足条件,所以执行的是宏定义中的b,没有执行++a,所以a最终为8

  5. 关于fseek() 参考:C库函数 - fseek()

  6. 转义字符分三种,一般转义字符,八进制转义字符和十六进制转义字符

    一般转义字符,如‘\b’,由两个字符表示,其实代表一个字符,这个代表退格字符

    八进制转义字符,如‘\007’,三位数字是八进制的,ASCII码为7的表示响铃

    十六进制转义字符,如’\xfe’,同样后面数字是所表示意思的Ascii码的十六进制表示,注意一定要有x,大小写都行

  7. 在C程序中逗号运算符的优先级最低,赋值运算符其次;j++是属于赋值语句;sizeof()属于一元运算符;

  8. C中&&(逻辑与)和&(按位与)

    • 按位与运用二进制进行计算,逻辑与比较符号两边的真假输出逻辑值。
    • 按位与对所有的表达式都要判断,逻辑与运算符第一个表达式不成立的话,后面的表达式不运算,直接返回。
    • 按位与&输出运算结果为不同的数值,逻辑与 && 输出逻辑值true或者 false。
  9. 两个指针变量不可以相加,因为指针变量相加没有意义。

  10. scanf函数不能指定输入精度,可以指定长度,比如%m.nf是不允许的,但是可以%mf(m为整数)。scanf("%7.2f",&a);不合法

  11. 多态类中的虚函数表建立在编译阶段。对类的编译,内存分布不太了解。

  12. const int* p是常量指针,p可以改变,*p不能改变int* const p = &a是指针常量,p不可以改变,*p能改变;记忆方法:const后面是p,p不能改变,const后面是*p,*p不能改变;还有一种const int* const p = &a,p和*p都不能改变。

  13. C++程序执行时,内存划分4个区域。不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。代码区和全局区是在程序运行前划分,堆栈是在程序运行后划分。

    • 代码区:存放函数体和二进制代码,由操作系统负责管理(共享、只读)
    • 全局区:存放全局变量和静态变量以及字符串,全局常量(程序结束后,由系统自动回收)
    • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量,局部常量等
    • 堆区:由程序员分配释放,若程序员不释放,程序结束时,由系统自动回收
  14. C++用new关键字请求内存,用delete释放内存,参考:C++ 动态内存

  15. C没有引用,C++引用变量是一个别名,参考:C++引用;使用引用传参;使用引用作为函数返回值引用的本质是指针常量;

  16. 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝析构时重复释放堆带来的问题

  17. 类成员中有其他类对象,构造时先构造成员中的类对象,再构造自身;析构时相反。

  18. 静态成员变量:所有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化;静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量;

  19. 1
    2
    3
    4
    5
    ostream& operator<<(ostream& cout, Class &c)
    { // c++重载<<运算符
    cout << c.m_A; // 打印成员变量
    return cout; // 链式编程
    }