【STM32H7教程】第24章 STM32H7的Cache解读(非常重要)
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第24章 STM32H7的Cache解读(非常重要)
本章教程为大家讲解STM32H7初学过程中最重要的一个知识点Cache。Cache在STM32H7的高性能发挥中占着举足轻重的作用。所以掌握好Cache是提升STM32H7性能的关键一步。
24.1 初学者重要提示
24.2 引出问题
24.3 支持的Cache配置
24.4 四种Cache(MPU)配置的读写操作流程
24.5 面对繁冗复杂的Cache配置,推荐方式和安全隐患解决办法
24.6 Cache的相关函数
24.7 总结
24.1 初学者重要提示
- 学习本章节前,务必保证已经学习了第23章的MPU知识。
- 本章是半年的实践经验总结,非常具有参考价值,而且是入门STM32H7的必学章节。
- Cache的熟练运用需要不断的经验积累。对于初学者来说,可能无法一下子理解所有知识点,但是一定要的花时间多读几遍,随着后面章节的不断运用,认识会不断的深入。
24.2 引出问题
当前芯片厂商出的M7内核芯片基本都做了一级Cache支持,Cache又分数据缓存D-Cache和指令缓冲I-Cache,STM32H7的数据缓存和指令缓存大小都是16KB。对于指令缓冲,用户不用管,这里主要说的是数据缓存D-Cache。以STM32H7为例,主频是400MHz,除了TCM和Cache以400MHz工作,其它AXI SRAM,SRAM1,SRAM2等都是以200MHz工作。数据缓存D-Cache就是解决CPU加速访问SRAM。
如果每次CPU要读写SRAM区的数据,都能够在Cache里面进行,自然是最好的,实现了200MHz到400MHz的飞跃,实际是做不到的,因为数据Cache只有16KB大小,总有用完的时候。
对于使能了Cache的SRAM区,要分读写两种情况考虑。
- 读操作:
如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。
- 写操作:
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。

24.3 支持的Cache配置
(这个知识点在上一章节进行了详细说明,这里再简述下核心内容)
Cache的配置是通过MPU来设置的,通常只用到下几种方式。

其中的TEX是用来设置Cache策略的,C是Cache,B是缓冲用来配合Cache设置的,而S是共享,用来解决多总线或者多核访问时的同步问题。MPU配置的时候,最主要的也是配置这几个参数。
Cache支持的策略有如下四种:

有了这四种方式,就可以正式进入本章的主题,Cache的读写操作是如何工作的,下面分这四种情况做介绍。
24.4 四种Cache(MPU)配置的读写操作流程
24.4.1 配置Non-cacheable
这个最好理解,就是正常的读写操作,无Cache。
对应四种MPU配置如下:
- TEX = 000 C=0 B=0 S=忽略此位,强制为共享
- TEX = 000 C=0 B=1 S=忽略此位,强制为共享
- TEX = 001 C=0 B=0 S=0
- TEX = 001 C=0 B=0 S=1
24.4.2 配置Write through,read allocate,no write allocate
注意,M7内核只要开启了Cache,read allocate就是开启的。
- 使能了此配置的SRAM缓冲区写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域,那么会同时写到Cache里面和SRAM里面;如果没有,就用到配置no write allocate了,意思就是CPU会直接往SRAM里面写数据,而不再需要在Cache里面开辟空间了。
在写Cache命中的情况下,这个方式的优点是Cache和SRAM的数据同步更新了,没有多总线访问造成的数据一致性问题。缺点也明显,Cache在写操作上无法有效发挥性能。
- 使能了此配置的SRAM缓冲区读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,就可以直接从Cache里面读取。如果没有,就用到配置read allocate了,意思就是在Cache里面开辟区域,将SRAM区数据加载进来,后续的操作,CPU可以直接从Cache里面读取,从而时间加速。
安全隐患,如果Cache命中的情况下,DMA写操作也更新了SRAM区的数据,CPU直接从Cache里面读取的数据就是错误的。
- 对应的两种MPU配置如下:
TEX = 000 C=1 B=0 S=1
TEX = 000 C=1 B=0 S=0
24.4.3 配置Write back,read allocate,no write allocate
注意,M7内核只要开启了Cache,read allocate就是开启的。
- 使能了此配置的SRAM缓冲区写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域,那么会写到Cache里面,而不会立即更新SRAM;如果没有,就用到配置no write allocate了,意思就是CPU会直接往SRAM里面写数据,而不再需要在Cache里面开辟空间了。
安全隐患,如果Cache命中的情况下,此时仅Cache更新了,而SRAM没有更新,那么DMA直接从SRAM里面读出来的就是错误的。
- 使能了此配置的SRAM缓冲区读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,就可以直接从Cache里面读取。如果没有,就用到配置read allocate了,意思就是在Cache里面开辟区域,将SRAM区数据加载进来,后续的操作,CPU可以直接从Cache里面读取,从而时间加速。
安全隐患,如果Cache命中的情况下,DMA写操作也更新了SRAM区的数据,CPU直接从Cache里面读取的数据就是错误的。
- 对应两种MPU配置如下:
TEX = 000 C=1 B=1 S=1
TEX = 000 C=1 B=1 S=0
24.4.4 配置Write back,read allocate,write allocate
注意,M7内核只要开启了Cache,read allocate就是开启的。
- 使能了此配置的SRAM缓冲区写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域,那么会写到Cache里面,而不会立即更新SRAM;如果没有,就用到配置write allocate了,意思就是CPU写到往SRAM里面的数据,会同步在Cache里面开辟一个空间将SRAM中写入的数据加载进来,如果此时立即读此SRAM区,那么就会有很大的速度优势。
安全隐患,如果Cache命中的情况下,此时仅Cache更新了,而SRAM没有更新,那么DMA直接从SRAM里面读出来的就是错误的。
- 使能了此配置的SRAM缓冲区读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,就可以直接从Cache里面读取。如果没有,就用到配置read allocate了,意思就是在Cache里面开辟区域,将SRAM区数据加载进来,后续的操作,CPU可以直接从Cache里面读取,从而时间加速。
安全隐患,如果Cache命中的情况下,DMA写操作也更新了SRAM区的数据,CPU直接从Cache里面读取的数据就是错误的。
这个配置被誉为可以最大程度发挥Cache性能,不过具体应用仍需具体分析。
- 对应两种MPU配置如下:
TEX = 001 C=1 B=1 S=1
TEX = 001 C=1 B=1 S=0
24.4.5 共享配置是个隐形的大坑
STM32H7编程手册对其的描述是多核共享。

而H7的应用笔记对齐的描述是开启共享基本等同于关闭Cache。

实际测试下面四种开Cache的情况,开关共享对缓冲区的大批量数据的读操作影响很大,基本差出两倍,而写操作基本没有影响,也许这就是所谓的多总线同步读造成的。另外共享开关仅对开启了Cache的情况下有影响,而对于关闭了Cache的情况是没有影响的,开不开没关系。
24.4.6 总结这几种方式的几个关键知识点
- Cortex-M7内核的L1 Cache由多行内存区组成,每行有32字节,每行都配有一个地址标签。数据缓冲DCache是每4行为一组,称为4-way set associative。而指令缓冲区ICache是2行为一组,这样节省地址标签,不用每个行都标记一个地址。
- 对于读操作,只有在第1次访问指定地址时才会加载到Cache,而写操作的话,可以直接写到内存中(write-through模式)或者放到Cache里面,后面再写入(write-back模式)。
- 如果采用的是Write back,Cache line会被标为dirty,等到此行被evicted时,才会执行实际的写操作,将Cache Line里面的数据写入到相应的存储区。
- Cache命中是访问的地址落在了给定的Cache Line里面,所以硬件需要做少量的地址比较工作,以检查此地址是否被缓存。如果命中了,将用于缓存读操作或者写操作。如果没有命中,则分配和标记新行,填充新的读写操作。如果所有行都分配完毕了,Cache控制器将支持eviction操作。根据Cache Line替换算法,一行将被清除Clean,无效化Invalid或者重新配置。数据缓存和指令缓存是采用的伪随机替换算法。
- Cache支持的4种基本操作,使能,禁止,清空和无效化。Clean清空操作是将Cache Line中标记为dirty的数据写入到内存里面,而无效化Invalid是将Cache Line标记为无效,即删除操作。
24.5 面对繁冗复杂的Cache配置,推荐方式和安全隐患解决办法
- 推荐使用128KB的TCM作为主RAM区,其它的专门用于大缓冲和DMA操作等。
- Cache问题主要是CPU和DMA都操作这个缓冲区时容易出现,使用时要注意。
- Cache配置的选择,优先考虑的是WB,然后是WT和关闭Cache,其中WB和WT的使用中可以配合ARM提供的函数解决上面说到的隐患问题(见本章24.6小节)。但不是万能的,在不起作用的时候,直接暴力选择函数SCB_CleanInvlaidateDCache解决。关于这个问题,在分别配置以太网MAC的描述符缓冲区,发送缓冲区和接收缓冲区时尤其突出。
24.6 Cache的相关函数
CMSIS软件包的core_cm7.h文件为Cache的配置提供了11个函数:
- SCB_EnableICache
- SCB_DisableICache
- SCB_InvalidateICache
- SCB_EnableDCache
- SCB_DisableDCache
- SCB_InvalidateDCache
- SCB_CleanDCache
- SCB_CleanInvalidateDCache
- SCB_InvalidateDCache_by_Addr
- SCB_CleanDCache_by_Addr
- SCB_CleanInvalidateDCache_by_Addr
下面将这几个函数依次做个讲解。其中前三个函数是指令Cache,比较容易掌握。重点是后面几个数据Cache函数。由于函数SCB_CleanInvalidateDCache,SCB_CleanDCache和SCB_InvalidateDCache是对整个Cache的操作,所以比最后的三个函数SCB_InvalidateDCache_by_Addr,SCB_CleanDCache_by_Addr和SCB_CleanInvalidateDCache_by_Addr要耗时,当然,如果用户操作的存储器超过了数据Cache的大小,即16KB,那么就跟前三个函数没有区别了。
24.6.1 函数SCB_EnableICache
函数原型:
__STATIC_INLINE void SCB_EnableICache (void)
{
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
__DSB();
__ISB();
SCB->ICIALLU = 0UL; /* invalidate I-Cache */
__DSB();
__ISB();
SCB->CCR |= (uint32_t)SCB_CCR_IC_Msk; /* enable I-Cache */
__DSB();
__ISB();
#endif
}
函数描述:
此函数用于使能指令Cache,系统上电后优先初始化即可。
注意事项:
- __STATIC_INLINE:
表示内联函数,这种类型函数的作用就是将函数直接嵌入到调用此函数的代码中,从而降低调用此函数所占用的时间。
- __DMB指令:
Data Memory Barrier(数据存储器隔离),DMB 指令保证所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的存储器访问操作。
- __DSB指令:
Data Synchronization Barrier(数据同步隔离),比DMB严格,当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令。
- __ISB指令:
Instruction Synchronization Barrier(指令同步隔离),它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
24.6.2 函数SCB_DisableICache
函数原型:
__STATIC_INLINE void SCB_DisableICache (void)
{
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
__DSB();
__ISB();
SCB->CCR &= ~(uint32_t)SCB_CCR_IC_Msk; /* disable I-Cache */
SCB->ICIALLU = 0UL; /* invalidate I-Cache */
__DSB();
__ISB();
#endif
}
函数描述:
此函数用于禁止指令Cache。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.3 函数SCB_InvalidateICache
函数原型:
__STATIC_INLINE void SCB_InvalidateICache (void)
{
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
__DSB();
__ISB();
SCB->ICIALLU = 0UL;
__DSB();
__ISB();
#endif
}
函数描述:
此函数用于将指令Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的指令。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.4 函数SCB_EnableDCache
函数原型:
__STATIC_INLINE void SCB_EnableDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways; SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB(); ccsidr = SCB->CCSIDR; /* invalidate D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCISW = (((sets << SCB_DCISW_SET_Pos) & SCB_DCISW_SET_Msk) |
((ways << SCB_DCISW_WAY_Pos) & SCB_DCISW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U);
__DSB(); SCB->CCR |= (uint32_t)SCB_CCR_DC_Msk; /* enable D-Cache */ __DSB();
__ISB();
#endif
}
函数描述:
此函数用于使能数据Cache,系统上电后优先初始化即可。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.5 函数SCB_DisableDCache
函数原型:
__STATIC_INLINE void SCB_DisableDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
register uint32_t ccsidr;
register uint32_t sets;
register uint32_t ways; SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB(); SCB->CCR &= ~(uint32_t)SCB_CCR_DC_Msk; /* disable D-Cache */
__DSB(); ccsidr = SCB->CCSIDR; /* clean & invalidate D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCISW = (((sets << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((ways << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U); __DSB();
__ISB();
#endif
}
函数描述:
此函数用于禁止数据Cache。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明
24.6.6 函数SCB_InvalidateDCache
函数原型:
__STATIC_INLINE void SCB_CleanDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways; SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB(); ccsidr = SCB->CCSIDR; /* clean D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCSW = (((sets << SCB_DCCSW_SET_Pos) & SCB_DCCSW_SET_Msk) |
((ways << SCB_DCCSW_WAY_Pos) & SCB_DCCSW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U); __DSB();
__ISB();
#endif
}
函数描述:
此函数用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.7 函数SCB_CleanDCache
函数原型:
__STATIC_INLINE void SCB_CleanDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways; SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB(); ccsidr = SCB->CCSIDR; /* clean D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCSW = (((sets << SCB_DCCSW_SET_Pos) & SCB_DCCSW_SET_Msk) |
((ways << SCB_DCCSW_WAY_Pos) & SCB_DCCSW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U); __DSB();
__ISB();
#endif
}
函数描述:
此函数用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.8 函数SCB_CleanInvalidateDCache
函数原型:
__STATIC_INLINE void SCB_CleanInvalidateDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways; SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB(); ccsidr = SCB->CCSIDR; /* clean & invalidate D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCISW = (((sets << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((ways << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U); __DSB();
__ISB();
#endif
}
函数描述:
此函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
注意事项:
__STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
24.6.9 函数SCB_InvalidateDCache_by_Addr
函数原型:
__STATIC_INLINE void SCB_InvalidateDCache_by_Addr (uint32_t *addr, int32_t dsize)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
int32_t op_size = dsize;
uint32_t op_addr = (uint32_t)addr;
int32_t linesize = ; /* in Cortex-M7 size of cache line is fixed to 8 words (32 bytes) */ __DSB(); while (op_size > ) {
SCB->DCIMVAC = op_addr;
op_addr += (uint32_t)linesize;
op_size -= linesize;
} __DSB();
__ISB();
#endif
}
函数描述:
此函数与本章24.6.6小节中讲解的函数作用一样,区别是这里可以指定地址和存储区大小。用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。
注意事项:
- __STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
- 第1个参数addr : 操作的地址一定要是32字节对齐的,即这个地址对32求余数等于0。
- 第2个参数dsize :一定要是32字节的整数倍。
24.6.10 函数SCB_CleanDCache_by_Addr
函数原型:
__STATIC_INLINE void SCB_CleanDCache_by_Addr (uint32_t *addr, int32_t dsize)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
int32_t op_size = dsize;
uint32_t op_addr = (uint32_t) addr;
int32_t linesize = ; /* in Cortex-M7 size of cache line is fixed to 8 words (32 bytes) */ __DSB(); while (op_size > ) {
SCB->DCCMVAC = op_addr;
op_addr += (uint32_t)linesize;
op_size -= linesize;
} __DSB();
__ISB();
#endif
}
函数描述:
此函数与本章24.6.7小节中讲解的函数作用一样,区别是这里可以指定地址和存储区大小。用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。
注意事项:
- __STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
- 第1个参数addr : 操作的地址一定要是32字节对齐的,即这个地址对32求余数等于0。
- 第2个参数dsize :一定要是32字节的整数倍。
24.6.11 函数SCB_CleanInvalidateDCache_by_Addr
函数原型:
__STATIC_INLINE void SCB_CleanInvalidateDCache_by_Addr (uint32_t *addr, int32_t dsize)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
int32_t op_size = dsize;
uint32_t op_addr = (uint32_t) addr;
int32_t linesize = ; /* in Cortex-M7 size of cache line is fixed to 8 words (32 bytes) */ __DSB(); while (op_size > ) {
SCB->DCCIMVAC = op_addr;
op_addr += (uint32_t)linesize;
op_size -= linesize;
} __DSB();
__ISB();
#endif
}
函数描述:
此函数与本章24.6.8小节中讲解的函数作用一样,区别是这里可以指定地址和存储区大小。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
注意事项:
- __STATIC_INLINE,__DMB,__DSB和__ISB的作用看本章24.6.1小节的说明。
- 第1个参数addr : 操作的地址一定要是32字节对齐的,即这个地址对32求余数等于0。
- 第2个参数dsize :一定要是32字节的整数倍。
24.7 总结
本章节就为大家讲解这么多,务必要反复多读几遍,能理解多少算多少,随着后续章节的学习再回过头来再学习。
【STM32H7教程】第24章 STM32H7的Cache解读(非常重要)的更多相关文章
- C#4.0图解教程 - 第24章 反射和特性 - 1.反射
24.1 元数据和反射 有关程序及类型的数据被成为 元数据.他们保存在程序集中. 程序运行时,可以查看其他程序集或其本身的元数据.一个运行的程序查看本身元数据或其他程序的元数据的行为叫做 反射. 24 ...
- C#4.0图解教程 - 第24章 反射和特性 – 2.特性
1.特性 定义 Attribute用来对类.属性.方法等标注额外的信息,贴一个标签(附着物) 通俗:给 类 或 类成员 贴一个标签,就像航空部为你的行李贴一个标签一样 注意,特性 是 类 和 类的成员 ...
- 【STM32H7教程】第3章 STM32H7整体把控
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第3章 STM32H7整体把控 初学STM32H7一 ...
- 【STM32H7教程】第22章 STM32H7的SysTick实现多组软件定时器
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第22章 STM32H7的SysTick实现 ...
- 【STM32H7教程】第34章 STM32H7的定时器应用之TIM1-TIM17的PWM实现
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第34章 STM32H7的定时器应用之TIM1-T ...
- 【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第30章 STM32H7的USART应用之八个串口 ...
- 【STM32H7教程】第16章 STM32H7必备的HAL库API(重要)
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第16章 STM32H7必备的HAL库API(重要 ...
- 【STM32H7教程】第58章 STM32H7的硬件JPEG应用之图片解码显示
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第58章 STM32H7的硬件JPEG应用之图片解 ...
- 【STM32H7教程】第56章 STM32H7的DMA2D应用之刷色块,位图和Alpha混合
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第56章 STM32H7的DMA2D应用之刷色块, ...
随机推荐
- linux下通过命令连接wifi
故事背景:我司是做新零售的,机器支持4G.wifi.网线,可能会涉及到网络的切换和连接 项目需求:用户在web端输入wifi名称和密码,客户端可以通过服务端下发的信息进行连接 技术调研:之前提到过nm ...
- Zabbix自定义监控项(模板)
虽然Zabbix提供了很多的模板(简单理解为监控项的集合),在zabbix界面点击share按钮就可以直接跳到模板大全的官方网站,但是由于模板内的监控项数量太多不好梳理且各种模板质量参差不齐,还是建议 ...
- 11. Go 语言网络编程
Go 语言网络编程 Go语言在编写 web 应用方面非常得力.因为目前它还没有 GUI(Graphic User Interface 图形化用户界面)的框架,通过文本或者模板展现的 html 界面是目 ...
- 解决RubyMine中puts中文显示乱码的问题
一个简单的ruby代码,puts一个中文,显示乱码 # -*- coding: utf-8 -*- puts "你好" require_relative 'calc.rb' # r ...
- CentOS7升级OpenSSL版本
1.CentOS7.6默认安装的openssl版本为 # 查看openssl版本 openssl version 2.下载最新的openssl wget https://www.openssl.org ...
- Docker 系列之 常用镜像
Ubuntu 实战 操作 # 拉取 18.04 版本的 Ubuntu 镜像 docker pull ubuntu:latest # 以交互方式运行并进入 ubuntu 容器环境 docker run ...
- python distutils 基本打包与发布
distutils 实现对package 包的发布 import math def showMsg(a): return a * a * a a = 10 print('%d 的三次方是 %d' % ...
- GO基础之切片
一.什么是切片 Go语言切片是对数组的抽象. Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"): 与数组相比切 ...
- FCC---Use the CSS Transform scale Property to Change the Size of an Element
To change the scale of an element, CSS has the transform property, along with its scale() function. ...
- Hadoop完全分布式搭建流程
centos7 搭建完全分布式 Hadoop 环境 SSR 前言 本次教程是以先创建 四台虚拟机 为基础,再配置好一台虚拟机的情况下,直接复制文件到另外的虚拟机中(这样做大大简化了安装流程) 且本次 ...