大家平时进行MCU软件开发时,基本上都会有一个初始化过程(如果没有的话,就不知道该说什么好了)。我们的硬件寄存器、模块的配置、软件数据结构、系统初始状态等,都是需要进行初始化设置的,以便于把控系统的初始运行状态。那么,初始化过程中一般会进行哪些工作呢?
1、初始化延时一段时间
在较多工程代码初始化阶段都会看到一个Delay_ms(XXX);的语句,有些小伙伴可能没怎么在意,延时就延时呗,有什么大惊小怪的。有些小伙伴认为是防止MCU跑飞等等之类的原因,其实这个语句也是需要根据具体情况来进行编写的,比如之前就有一个项目需要快速启动,那么初始化延时就是不可取的。
1//伪代码 2#define SYS_INITIAL_DELY (200) 3void SystemInitial(void) 4{ 5 Delay_ms(SYS_INITIAL_DELY); //系统延时 6 sLowInitial(); //底层初始化 7 sFucInitial(); //功能初始化 8 //...... 9} 10void main(void) { 11 12 SystemInitial(); //系统初始化 13 //...... 14}
Delay一下的原因:
首先聊聊对于"等待MCU电源稳定防止跑飞"这个原因,多少有点牵强,因为Delay函数也是一种正常程序运行,如果MCU跑飞Delay函数也是无法缓解的。
如果是为了防止外设初始化失败可能还说得过去,毕竟对于MCU内部外设功能的初始化有时候需要一个稳定的电源,否则容易产生初始化不成功等等异常。
不过根据之前开发MCU的经验来看,主要是MCU与其他模块对接时候的上电运行门限存在差异,都知道大部分芯片都会有一个最低上电启动电压,电压太低芯片无法启动,电压如果在最低电压附近波动容易造成系统不稳定,如果外部芯片与MCU共用同一个电源,MCU上电运行电压门限低,而外部芯片门限高,当MCU已经开始进行配置外部芯片操作,而外部芯片才刚刚开始进入运行初始化,这样会导致MCU对外部芯片配置不成功等问题。
2、比较传统的初始化结构图
大家别小看一个简单的初始化工作,一个结构清晰,层次分明的初始化函数封装能够为你在以后的大工程项目中省下不少时间,还记得刚写代码的时候,那变量初始化一个乱字都形容不过来,往往前面刚赋值接下来又被清零了。下面为大家提供一个简单传统的MCU初始化层次结构图,大家也可以结合自身项目进行设计:
解释一下:
上面的结构图采用自底向上的初始化层次,先进行最基础的系统时钟和总线时钟的配置,以及一些CPU相关的底层初始化;对于MCU次底层的是各个外设的初始化比如UART、USB、SDIO等等;然后对于数据结构和模型初始化可以根据具体功能模块进行划分,最后是对EEPROM、FLASH等存储信息进行读取以恢复系统之前的默认参数和状态。
可以根据自身项目具体情况进行设计,比如含有RTOS、GUI等等组件初始化也是需要进行划分与归类的,这里仅仅提供一些思路。
二、让MCU告诉我更多信息 其实进行MCU软件开发的时候对本身并不是特别的熟悉,而且确实现在很多MCU都封装得特别好,比如MCU具体如何进行运算,在哪里取数据,又把数据送去哪里,一段代码会运算多长时间等等。导致一些朋友遇到一些bug就难以解决,所以如果能够更清楚的了解一款MCU那么就多一分对bug的把控能力,一定玩玩MCU,而不要被MCU耍了。好了,为了能够更好的把控MCU,我们得在今天的初始化主题这里干点事。
1、直接初始化输出基础类型大小
1void PrintTypeSize(void) 2{ 3 printf("Type Size:\n"); 4 printf("char : %u\n", sizeof(char)); 5 printf("short : %u\n", sizeof(short)); 6 printf("int : %u\n", sizeof(int)); 7 printf("long : %u\n", sizeof(long)); 8 printf("long long : %u\n", sizeof(long long)); 9 printf("void* : %u\n", sizeof(void *)); 10 printf("float : %u\n", sizeof(float)); 11 printf("double : %u\n", sizeof(double)); 12 //......Add your printf of your think 13}
解释一下:
这一段代码很简单吧,估计有很多朋友都没有真正在自己所用的平台上打印过,在学习C语言的过程中应该都知道这中间有些数据类型是与平台相关的,如果没有对这些基础类型的长度把控好,估计bug就来找你了.
同时大家在平时的编程过程中也一定要注意数据类型的范围,引起的后果可大可小哦,同时对于数据类型的深入学习可以参考往期文章。
2、字节对齐与顺序
这一部分算是开了几篇文章好好谈过的话题了,也是非常重要的内容,这里不做过多的解释,相关的检测算法也在其中为大家展示了,比较简单,所以大家如果不想查看MCU手册的时候直接可以使用对应的函数输出对应的结构体默认的对齐方式,字节序(也就是我们带小端)等等属性,所以这里建议大家在研发前期初始化过程中打印相关内容。一些人遇到一些小问题就会到处查资料,其实有些答案可以通过编程让MCU自己告诉我们,这也是快速开发一款新的MCU开发的基础。
3、运算时间的把控同样也是本文非常重要的一部分内容,也是非常想推荐给大家的一种评估和熟悉MCU性能方法,非常经典的宏定义,下面是在Windows下面模拟的程序,大家可以改造到自己的MCU上运行测试:
1#include <stdio.h> 2#include <stdlib.h> 3#include <windows.h> 4#include <math.h> 5/*************************************************** 6 * Fuction: 10次运行Operation 7 * Author :() 8 ***************************************************/ 9#define TEN_OPERATE_TIMES(x) do { x; x; x; x; x; x; x; x; x; x; } while (0) 10/*************************************************** 11 * Fuction: 50次运行Operation 12 * Author :() 13 ***************************************************/ 14#define FIFTY_OPERATE_TIMES(x) do {\ 15 TEN_OPERATE_TIMES(x); \ 16 TEN_OPERATE_TIMES(x); \ 17 TEN_OPERATE_TIMES(x); \ 18 TEN_OPERATE_TIMES(x); \ 19 TEN_OPERATE_TIMES(x); \ 20 } while (0) 21/*************************************************** 22 * Fuction: 答应对应运算op所需时间 23 * Author :() 24 ***************************************************/ 25#define PRINT_TIME(name, operate, count) do { \ 26 unsigned char i = 0;\ 27 LARGE_INTEGER StartTime;\ 28 LARGE_INTEGER EndTime;\ 29 LARGE_INTEGER Freq;\ 30 QueryPerformanceFrequency (&Freq);\ 31 QueryPerformanceCounter(&StartTime);\ 32 for (i = 0; i < count; i++) { \ 33 FIFTY_OPERATE_TIMES(operate); \ 34 } \ 35 QueryPerformanceCounter(&EndTime);\ 36 printf("%-8s %7.8f ms\n", name, (EndTime.QuadPart -StartTime.QuadPart )*1000.0f*1000.0f/Freq.QuadPart/ (double)(count * 50.0));\ 37 } while (0) 38 39 40/*************************************************** 41 * Fuction: 变量定义区域 42 * Author :() 43 ***************************************************/ 44volatile float fv = 1.0; 45volatile float fv_out = 0.8; 46/*************************************************** 47 * Fuction: 测试main函数 48 * Author :() 49 ***************************************************/ 50int main(int argc, char *argv[]) { 51 52 PRINT_TIME("Mul :", fv_out *= fv, 200); 53 PRINT_TIME("Div :", fv_out /= fv, 200); 54 PRINT_TIME("sin(x):", fv_out = sinf(fv), 100); 55 PRINT_TIME("cos(x):", fv_out = cosf(fv), 100); 56 57 return 0; 58}
运行结果看一看:
解析一下:
首先这里主要是宏定义和do{}while(0);的结合使用案例,大家可以学习一下这种定义方式。
设计亮点 : 前面两个宏定义的设计大家应该会有疑问为什么不在for循环里面直接cnt累计时间,最后累计好足够的时间不就可以计算每次运算的时间了吗 ?
大家需要注意的是for循环也是由几条汇编语句构成的,如果所测试的运算操作时间非常短,测试的最小时间也不会小于for运算的时间,这样测量误差较大,所以这里构造了多次运算,从而与for语句运算时间不在一个运行时间等级上便可以提高精度。
如果很多小伙伴听得迷迷糊糊,上图!!!!