技术标签: stm32 嵌入式硬件 arm 开发语言 单片机
很多应届生或嵌入式新手在有意向寻找嵌入式软件开发相关工作时,总是觉得不知道该准备些什么,感到无从下手。造成这种现象的原因主要是因为嵌入式需要具备的知识比较杂,既有编程、硬件电路、计算机原理相关的知识,也有通信、操作系统和非计算机行业相关知识。本文详细介绍嵌入式软件开发需要具备的各类知识,帮助想要从事嵌入式相关职业的人能够快速的掌握嵌入式的基本知识。全部掌握后,几乎可以确保读者可以通过市面上大都数初级嵌入式软件开发的技术面试。
以下将通过C/C++必备知识、通信必备知识、操作系统、计算机基础知识和非计算机概念几个部分具体讲解。
“filename.h”是从本项目里搜索filename.h,<filename.h> 是从标准库里搜索filename.h文件
问题:“静态全局变量”和“非静态全局变量”有什么区别? “静态局部变量”和“非静态局部变量”有什么区别? “静态函数”和“非静态函数”有什么区别?
静态全局变量只在本文件中定义,其他文件不能引用.
局部变量所在函数每次调用的时候都会被重新分配存储空间,函数结束后,就会回收该存储空间。静态局部变量不会,始终保持当前值。
calloc在动态分配完内存后,将内存空间置为零。malloc不初始化,里边数据是随机的脏数据。
静态全局变量:在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。静态变量在应用层面上主要是限定作用域。
静态全局变量有以下特点:
代码区 | low address |
---|---|
全局数据区堆区栈区 | high address |
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
void staticFun(void)
{
static uint8_t data = 0;
data++;
printf("static function data = %d\r\n",data);
}
void NostaticFun(void)
{
uint8_t data = 0;
data++;
printf("no static function data = %d\r\n",data);
}
int main()
{
staticFun();
staticFun();
staticFun();
NostaticFun();
NostaticFun();
NostaticFun();
return 0;
}
执行此程序,主函数会先调用三次staticFun();函数,再调用三次NostaticFun();函数。最后的输出结果为:
1
2
3
1
1
1
因为每次NostaticFun中的data 都会被重新定义,而staticFun中的data不会重复定义。
用来修饰不可赋值的变量,如果一个变量在声明初始化之后不希望被修改,可以声明为const;
const修饰的变量应该进行初始化;
const修饰的变量有可能改变,部分编译器可用scanf修改;
const常用来修饰函数的形参,保证该参数在函数内部不会被修改。
extern表明变量或者函数是定义在其他其他文件中的。用来修饰外部变量(全局),表示该变量在其他文件中定义。首先讲一下声明与定义,
声明不等于定义,声明只是指出了变量的名字,并没有为其分配存储空间;定义指出变量名字同时为变量分配存储空间,定义包含了声明。extern是用来声明全局变量的。注意:在程序中一个变量可以声明多次,但只能定义一次。
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。
在嵌入式系统编程中,将变量声明为volatile通常用于处理硬件寄存器或在不同线程或中断服务例程(ISR)之间共享的变量。它确保编译器不会优化掉对该变量的某些操作,确保其值始终从内存中读取而不是从缓存中读取,并且对变量的写入立即对代码的其他部分可见。
1.全局变量
从静态存储区域分配,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用。如果程序是由多个源文件构成的,那么全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,但必须在其他文件中通过使用extern关键字来声明该全局变量。
2.全局静态变量
从静态存储区域分配,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用。与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用。
3.局部变量
从栈上分配,其作用域只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,也即该变量的生命周期和该函数同在。
4.局部静态变量
从静态存储区域分配,其在第一次初始化后就一直存在直到程序结束。该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。
在 C/C++ 中,结构体/类是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。编译器为每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。如果在 32 位的机器下,一个int类型的地址为0x00000004,那么它就是自然对齐的。同理,short 类型的地址为0x00000002,那么它就是自然对齐的。char 类型就比较 “随意” 了,因为它本身长度就是 1 个字节。自然对其的前提下:
char 偏移量为sizeof(char) 即 1 的倍数
short 偏移量为sizeof(short) 即 2 的倍数
int 偏移量为sizeof(int) 即 4 的倍数
float 偏移量为sizeof(float) 即 4 的倍数
double 偏移量为sizeof(double) 即 8 的倍数
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
struct asd1{
char a;
int b;
short c;
};//12字节
struct asd2{
char a;
short b;
int c;
};//8字节
上面两个结构体拥有相同的数据成员 char、short 和 int,但由于各个成员按照它们被声明的顺序在内存中顺序存储,所以不同的声明顺序导致了结构体所占空间的不同。具体如下图:
2. 看到上面的第二张图,有的人可能会有疑问,为什么 short 不是紧挨着 char 呢?其实这个原因在上面已经给出了答案——自然对齐。为此,我们可以创建结构体验证自然对齐的规则。实验很简单,在原本 short 类型变量前后添加 char 类型,看结果是怎样的。实验二:
struct asd3{
char a;
char b;
short c;
int d;
};//8字节
struct asd4{
char a;
short b;
char c
int d;
};//12字节
3. 当数据成员中有 double 和 long 时,情况又会有一点变化。还是以上面的结构体 asd1 和 asd2 为基础,都添加 double 型数据成员。来看看结果是什么,实验三:
struct asd1{
char a;
int b;
short c;
double d;
};//24个字节
struct asd2{
char a;
short b;
int c;
double d;
};//16个字节
只添加了一个 double,但 struct asd1 的大小从 12 变到了 24。而 struct asd2 的大小从 8 变到了 16。不需要迷惑,因为这和 double 的自然对其有关(需要注意)。原本的 asd1 占 12 个字节大小,但是 double 对齐需要是 8 的倍数,所以在 short 后面又填充了 4 个字节。此时,asd1 的占 16 个字节,再加上 double 的 8 个字节就成了 24 个字节。而 asd2 没有这个问题,它原本占 8 个字节。因为正好能对齐,所以添加 double 后占 16 个字节。具体情况如下图所示:
4. 指定对齐值
在缺省情况下,C 编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令 #pragma pack (n),C 编译器将按照 n 个字节对齐。
使用伪指令 #pragma pack (),取消自定义字节对齐方式。
实验四:
#pragma pack(4)
struct asd5{
char a;
int b;
short c;
float d;
char e;
};//20
#pragma pack()
#pragma pack(1)
struct asd6{
char a;
int b;
short c;
float d;
char e;
};//12
#pragma pack()
使用 #pragma pack (value) 指令将结构体按相应的值进行对齐。两个结构体包含同样的成员,但是却相差 8 个字节。难道我们只需要通过简单的指令就能完成内存对齐的工作吗?其实不是的。上面的对齐结果如下:
以 32 位机器为例,CPU 取的字长是 32 位。所以上面的对齐结果会这样带来的问题是:访问未对齐的内存,处理器需要作两次内存访问。如果我要获取 int 和 float 的数据,处理器需要访问两次内存,一次获取 “前一部分” 的值,一次获取 “后一部分” 的值。这样做虽然减少了空间,但是增加访问时间的消耗。其实最理想的对齐结果应该是:
ps.使用 #pragma pack(4) 可以让前面的实验三中的 asd1 少占用 4 字节。
对齐原则
数据类型自身的对齐值:对于 char 型数据,其自身对齐值为1,对于 short 型为2,对于 int,float,double 类型,其自身对齐值为 4,单位字节。
结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
指定对齐值:#pragma pack (value) 时的指定对齐值 value。
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
根据实际情况,有时需要把几种类型不同的数据,如一个整型变量、一个字符变量、一个实型变量存放在起始地址相同的同一段存储单元种。这三个变量在内存种所占的字节数不同,但都从同一个地址开始存放。这种几个类型不同的变量共同占用同一段内存的结构,称为“共用体”类型结构。共用体,也称为联合体。
union 共用体名
{
成员表列
};
共用体变量的所有成员共享同一段存储空间,这个存储空间等于共用体变量种占据内存字节数最多的成员的字节数。
##用来连接前后两个参数,把它们变成一个字符串。
例子如下:
#define main(x,y) x##y
int xy=1;
cout < < main(x,y) < < endl;
将会使编译器把
cout < < main(x,y) < < endl;
解释为
cout < < xy < < endl;
理所当然,将会在标准输出处显示’1’。
从此可以看出,x##y的效果就是将x和y连在一起了。
而#define main(x,y) x##y 则相当于把main(x,y)等价于x##y
我们在调试程序时,经常会遇到某段功能的实现,写了两种版本的程序,但调试时又不想来回切换。,这时候我们可以使用条件变量。
比如:想测试__set_FAULTMASK(1);和__disable_fault_irq();的区别,就可以使用如下方式,只需要更改#if后面是1还是0就可以选择是使用哪段程序。
#if 1
//
__set_FAULTMASK(1);
NVIC_SystemReset();
#else
__disable_irq();
delay_ms(1000);
__disable_fault_irq();
NVIC_SystemReset();
#endif
C库函数是我们开发过程中必不可少的,其中面试中突出考察的大多为string.h中的库函数。
memcpy函数的用法,memcpy (void* _Dst,void const* _Src,size_t _Size)
memcpy函数是将后面地址的内容一个数据一个数据放在前面的地址,注意,是先放低位。
_Size是字节数,也就是说如果是32位数组,两个数组值就应该是_Size就应该是4。
例子:
char a[8]={
0x12,0x34,0x56,0x78,0x90,0x14,0x52,0x46 };
short b=0;
memcpy(&b,a+1,2);
printf("b=%x", b);
此段代码的作用是把0x34和0x56拼接起来送到b,输出的最终结果是:0x5634。
void 指针可以指向任意类型的数据,就是说可以用任意类型的指针对 void 指针对 void 指针赋值。如果要将 void 指针 p 赋给其他类型的指针,则需要强制类型转换,就本例而言:a=(int *)p。在内存的分配中我们可以见到 void 指针使用:内存分配函数 malloc 函数返回的指针就是 void * 型,用户在使用这个指针的时候,要进行强制类型转换,也就是显式说明该指针指向的内存中是存放的什么类型的数据 (int )malloc(1024) 表示强制规定 malloc 返回的 void 指针指向的内存中存放的是一个个的 int 型数据。
int *Q;
void *P;
P=Q;
我们可以看到void指针类型是可以指向int *指针类型的。
在64位系统中,不管什么样的基类型,系统指针给指针变量分配的内存空间都是8字节,在C语言中,指针变量的“基类型”仅用来指定该指针变量可以指向的变量类型,并没有其他意思。 不管基类型是什么类型的指针变量,他仍然是指针变量,所以仍然占用 8 字节。
%*c表示忽略一个字符
此函数在嵌入式的日常开发中使用频繁,功能为:在字符串 A中查找第一次出现字符串B的位置。
C 标准库 - <string.h>
C 库函数 char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’。
C 标准库 - <stdlib.h
描述
C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)。
描述
C 库函数 int rename(const char *old_filename, const char *new_filename) 把 old_filename 所指向的文件名改为 new_filename。
参数
old_filename – 这是 C 字符串,包含了要被重命名/移动的文件名称。
new_filename – 这是 C 字符串,包含了文件的新名称。
串口(UART通用异步收发器,TTL)通讯是一种设备间的串行全双工通讯方式。由于UART是异步传输,没有传输同步时钟,为了保证数据的正确性,UART采用16倍数据波特率的时钟进行采样。
起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。
数据位:可以选择的值有5,6,7,8这四个值,可以传输这么多个值为0或者1的bit位。这个参数最好为8,因为如果此值为其他的值时当你传输的是ASCII值时一般解析肯定会出问题。理由很简单,一个ASCII字符值为8位,如果一帧的数据位为7,那么还有一位就是不确定的值,这样就会出错。
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
停止位:它是一帧数据的结束标志。可以是1bit、1.5bit、2bit的空闲电平。
空闲位:没有数据传输时线路上的电平状态。为逻辑1。
刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。接下来接着像这样一直传送。
空闲状态,线路处于高电平;当收到发送指令后,拉低线路的一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位,一帧数据发送完成。
空闲状态,线路处于高电平;当检测到线路的下降沿(高电平变为低电平)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备接收数据或存入缓冲。
UART的硬件连接比较简单,只需要两个设备的TXD和RXD相互反接,再将GND相连即可。
I2C(IIC)属于两线式串行总线,半双工通信方式,由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线,属于一主多从(一个主设备(Master),多个从设备(Slave))的总线结构,总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备多个主设备,多个从设备(外围 设备)。多主机会产生总线裁决问题。当多个主机同时想占用总线时,企图启动总线传输数据,就叫做总线竞争。I2C通过总线仲裁,以决定哪台主机控制总线。
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
物理I2C接口有两根双向线,串行时钟线(SCL)和串行数据线(SDA)组成,可用于发送和接收数据,但是通信都是由主设备发起,从设备被动响应,实现数据的传输。上拉电阻一般在4.7k~10k之间。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。一般情况下,数据线SDA和时钟线SCL都是处于上拉电阻状态。因为:在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI主从模式
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps。
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)
MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
硬件上为4根线。
DC+:控制电源+
DC-:控制电源-
DP+:驱动电源+
DP-:驱动电源-
电机的速度单位: 转/分。
减速比,即减速装置的传动比,是一种传动比,是指减速机构中瞬时输入速度与输出的速度的比值,用符号i表示。一般来说,减速比的表示方法是以1为分母,以:连接的輸入转速与输出转速之比。要是輸入转速为1500r/min,输出转速为25r/min,则其减速比为:i=60:1。
一般上位机控制下位机,下位机控制电机。上位机下发的速度一般单位是m/s,而下位机对电机下发的速度,应该是RPM。
LSpeed= Speed * REDUCITON_RATIO * 60 / (2 * PI* R)
一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。编译器会将一个程序分类为好几个部分,分别存储在 MCU 不同的存储区。
上面提到的 Program Size 包含以下几个部分:
Code:代码段,存放程序的代码部分;
RO-data:只读数据段,存放程序中定义的常量;
RW-data:读写数据段,存放初始化为非 0 值的全局变量;
ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;
编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址。
程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如下图左边部分所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。
msg = (rt_uint8_t*) rt_malloc (40);
const static uint32_t A = 0x000000FE; uint32_t B; uint8_t C = 0;
B 存放在 ZI 段中,系统启动后会自动初始化成零(由用户程序或编译器提供的一些库函数初始化成零)。C 变量则存放在 RW 段中,而 A 存放在 RO 段中。文章浏览阅读1.2w次,点赞17次,收藏166次。1、制作好U盘之后,把U盘接上需要安装系统的机器,台式机或一体机可在开机按F12键调出引导菜单,台式机安装的时候建议断开网线,笔记本开机按F12或者Fn+F12键调出引导菜单。2、出现菜单选项后,选择“Boot Menu”启动菜单回车,选择USB项回车启动开始安装系统。6、输入PIN密码,(如果主机支持生物识别设备,也可在输入PIN后,继续录入指纹或人脸)4、插入准备好的U盘,如果没有显示,点击刷新驱动器列表,点击下一步。7、选择同步设备,如不想同步,选择设置为新设备。3、选择制作U盘,点击下一步,_win11安装程序
文章浏览阅读1.8w次,点赞8次,收藏11次。 以前在神经网络训练中,只是对输入层数据进行归一化处理,却没有在中间层进行归一化处理。要知道,虽然我们对输入数据进行了归一化处理,但是输入数据经过 $ \sigma(WX+b) $ 这样的矩阵乘法以及非线性运算之后,其数据分布很可能被改变,而随着深度网络的多层运算之后,数据分布的变化将越来越大。如果我们能在网络的中间也进行归一化处理,是否对网络的训练起到改进作用呢?答案是肯定的。 这种在神经网络中间层也进行归一化处理,使训练效果更好的方法,就是批归一化Batch Normalization(BN)。_深度学习 那些情况 要做 归一化
文章浏览阅读1.2w次,点赞12次,收藏101次。#(Notice:以下所有经验也是我根据网上的经验整理的,如有侵权可以联系我删除,QQ 654303408。 有问题讨论也可联系我,QQ同上。)#(Tips:我是第一次开发,一个刚毕业的java工程师,我觉得我并非天赋异禀,我能学会,相信聪敏的你,一定可以)#(PS:目前微信拥有无可撼动的人口基数,越来越多的项目开发是基于微信小程序,或者APP。但是支付方式无非两种,一种是支付宝,一种是微信支..._小程序后台java支付接口
文章浏览阅读27次。第一个python Web程序——简单的Web服务器。与其它Web后端语言不同,Python语言需要自己编写Web服务器。如果你使用一些现有的框架的话,可以省略这一步;如果你使用Python CGI编程的话,也可以省略这一步;用Python建立最简单的web服务器利用Python自带的包可以建立简单的web服务器。在DOS里cd到准备做服务器根目录的路径下,输入命令:python -m Web服务..._pyjwt webserver
文章浏览阅读1.3w次,点赞3次,收藏23次。RMSE和MAE能很好的反应图像的重建结果与真实结果间的差异。_rmse与mae有换算公式吗
文章浏览阅读3.4k次。背景默认情况下,Gradle 单元测试,是无法使用 System.out.println 这样打印变量信息的,这会让我们debug变得非常麻烦。百度网上很多方案,,但都比较麻烦,也很容易踩坑,。换了个搜索姿势,google了下,原来方案如此简单。解决在你的模块下的build.gradle.kts添加如下的配置:tasks.withType<Test> { this.testLogging { this.showStandardStreams = true _gradle 打印日志 system. out.print
文章浏览阅读2.2k次,点赞2次,收藏11次。sqlmap在默认的的情况下除了使用char()函数防止出现单引号,没有对注入的数据进行修改,还可以使用–tamper参数对数据做修改来绕过waf等设备。命令格式:sqlmap -u [url] --tamper [模块名]通过使用whereis sqlmap查看sqlmap安装路径,自带的脚本一般是在usr/share/sqlmap/tamper下,我的是1.6.3版本一共有66个自带脚本下边引一些常用的脚本:apostrophemask.py适用数据库:ALL作用_sqlmap绕过脚本
文章浏览阅读1.7k次。windows:\r\nlinux:\rmac:\n_分隔符 换行
文章浏览阅读4.2k次,点赞2次,收藏8次。喜欢「音乐杂谈」这个主题的朋友可以关注我的头条号,将会在不定期发表一些音乐理论以外的音乐话题的文章或者是音乐知识的干货 。(此文为混音师天职老师 发布于今日头条的原创文章,转载请告知并注明出处)通篇写作整理下来差不多花了7个小时,不管怎样,施舍点个赞吧。哈哈哈!继上一次「音乐杂谈41」混音选择困难第一期,给大家介绍了Waves全家桶的大部分压缩器之后,本篇,我们将来看看,Waves全家桶的大部分均..._waves功能详解
文章浏览阅读2.8k次。Android媒体包提供了可管理各种媒体类型的类。这些类可提供用于执行音频和视频操作。除了基本操作之外,还可提供铃声管理、脸部识别以及音频路由控制。本文说明了音频和视频操作。本文简介媒体包提供了可管理各种媒体类型的类。这些类可提供用于执行音频和视频操作。除了基本操作之外,还可提供铃声管理、脸部识别以及音频路由控制。本文说明了音频和视频操作。范围:_android 播放语言视频
文章浏览阅读2.7k次。Sublime & Markdown文章目录Sublime & Markdown安装 Sublime设置 Sublime安装插件Package ControlMarkdownEditingMarkdown PreviewLiveReloadauto-saveOmniMarkupPreviewerEvernote插件&主题插入图片Ctrl+vHTML语法Markdown语法...
文章浏览阅读695次。在调试RK3288 Android 8.1系统遇到一个问题:开机启动uboot logo过渡到kernel log的过程中会花掉直到没有显示,再出现kernel logo。分析:打印串口log时发现,uboot阶段显示一切正常,进入kernel以后就开始花掉了然后变成没有显示了,感觉像是慢慢掉电了一样,再继续查看log发现如下打印:[ 0.363167] Registered fiq deb..._mtk 转屏后 logo uboot 转kernel 显示异常