完整教程下载地址: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 初学者重要提示

  1. 学习本章节前,务必保证已经学习了第23章的MPU知识。
  2. 本章是半年的实践经验总结,非常具有参考价值,而且是入门STM32H7的必学章节。
  3. 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 总结这几种方式的几个关键知识点

  1. Cortex-M7内核的L1 Cache由多行内存区组成,每行有32字节,每行都配有一个地址标签。数据缓冲DCache是每4行为一组,称为4-way set associative。而指令缓冲区ICache是2行为一组,这样节省地址标签,不用每个行都标记一个地址。
  2. 对于读操作,只有在第1次访问指定地址时才会加载到Cache,而写操作的话,可以直接写到内存中(write-through模式)或者放到Cache里面,后面再写入(write-back模式)。
  3. 如果采用的是Write back,Cache line会被标为dirty,等到此行被evicted时,才会执行实际的写操作,将Cache Line里面的数据写入到相应的存储区。
  4. Cache命中是访问的地址落在了给定的Cache Line里面,所以硬件需要做少量的地址比较工作,以检查此地址是否被缓存。如果命中了,将用于缓存读操作或者写操作。如果没有命中,则分配和标记新行,填充新的读写操作。如果所有行都分配完毕了,Cache控制器将支持eviction操作。根据Cache Line替换算法,一行将被清除Clean,无效化Invalid或者重新配置。数据缓存和指令缓存是采用的伪随机替换算法。
  5. 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解读(非常重要)的更多相关文章

  1. C#4.0图解教程 - 第24章 反射和特性 - 1.反射

    24.1 元数据和反射 有关程序及类型的数据被成为 元数据.他们保存在程序集中. 程序运行时,可以查看其他程序集或其本身的元数据.一个运行的程序查看本身元数据或其他程序的元数据的行为叫做 反射. 24 ...

  2. C#4.0图解教程 - 第24章 反射和特性 – 2.特性

    1.特性 定义 Attribute用来对类.属性.方法等标注额外的信息,贴一个标签(附着物) 通俗:给 类 或 类成员 贴一个标签,就像航空部为你的行李贴一个标签一样 注意,特性 是 类 和 类的成员 ...

  3. 【STM32H7教程】第3章 STM32H7整体把控

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第3章   STM32H7整体把控 初学STM32H7一 ...

  4. 【STM32H7教程】第22章 STM32H7的SysTick实现多组软件定时器

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第22章       STM32H7的SysTick实现 ...

  5. 【STM32H7教程】第34章 STM32H7的定时器应用之TIM1-TIM17的PWM实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第34章       STM32H7的定时器应用之TIM1-T ...

  6. 【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第30章       STM32H7的USART应用之八个串口 ...

  7. 【STM32H7教程】第16章 STM32H7必备的HAL库API(重要)

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第16章       STM32H7必备的HAL库API(重要 ...

  8. 【STM32H7教程】第58章 STM32H7的硬件JPEG应用之图片解码显示

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第58章       STM32H7的硬件JPEG应用之图片解 ...

  9. 【STM32H7教程】第56章 STM32H7的DMA2D应用之刷色块,位图和Alpha混合

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第56章       STM32H7的DMA2D应用之刷色块, ...

随机推荐

  1. ERROR 1366 (HY000): Incorrect string value: '\xE9\x83\x91\xE5\xB7\x9E' for column 'aa' at row 1 MySQL 字符集

    ERROR 1366 (HY000): Incorrect string value: '\xE9\x83\x91\xE5\xB7\x9E' for column 'aa' at row 1创建表之后 ...

  2. Troubleshooting: High Version Count Issues

    --查询版本高的原因 select * from v$sql_shared_cursor where sql_id=''; Configuring Download the script in the ...

  3. SQL Server之批量清理数据库的死锁

    DECLARE killspid CURSOR  FOR (SELECT  CONVERT(VARCHAR(100), request_session_id)  FROM  sys.dm_tran_l ...

  4. leetcode题解:回文数

    判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向 ...

  5. pytorch 中的Variable一般常用的使用方法

    Variable一般的初始化方法,默认是不求梯度的 import torch from torch.autograd import Variable x_tensor = torch.randn(2, ...

  6. Violet 6 杯省选模拟赛 蒲公英

    https://www.luogu.com.cn/problem/P4168 题目 给$n$个数字,有$m$次询问,问$a_l, a_{l+1} , \dots , a_r$的众数是什么, $1\le ...

  7. plantuml-绘制状态图和活动图和部署图​

    背景 状态图:对象的所有状态,以及基于事件发生的状态改变的过程: 活动图:用例的工作流程: 部署图:系统的软硬件物理体系结构: 状态图 基本语法 元素 语法 说明 开始和结束状态 [*] 标识开始和结 ...

  8. 多线程七 AQS

    一 . 简介AQS AQS简介 在同步组件的实现中,AQS是核心部分,同步组件的实现者,通过使用AQS提供的模板方法 实现同步组件语义 AQS实现了对同步状态的管理以及阻塞线程进行排队,等待通知等等一 ...

  9. 【LeetCode】11. 盛最多水的容器

    题目 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...

  10. SSM框架之Mybatis(7)延迟加载、缓存及注解

    Mybatis(7)延迟加载.缓存及注解 1.延迟加载 延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据.延迟加载也称懒加载. **好处:**先从单表查询,需要时再从关联表去关 ...