Использование CCM на STM32F303CC

Как получить выигрыш по производительности в 30%, не используя новый микроконтроллер и без существенного изменения кода? В статье дается инструкция, как можно использовать альтернативный вид памяти - CCM RAM в микроконтроллерах STM32
2125
В избранное

Большинство  производителей микроконтроллеров используют одни и те же ядра (например, ARM Cortex), поэтому, чтобы конкурировать друг с другом и выделяться среди конкурентов, им нужно реализовывать в контроллерах различные "фишки". Конечно, часто все решает цена, но даже низкая цена не означает, что контроллер подходит к вашему проекту. При выборе контроллера нужно обращать внимание на различные периферийными устройствами, таймеры, режимы энергосбережения и т.д.

Тем не менее, иногда производители добавляют некоторые очень интересные функции в свои ядра, и в этой статье будет рассказано о Core-Coupled Memory (CCM), которая встречается в некоторых микроконтроллерах STM32. Для тестирования будет использоваться контроллер STM32F303CC с готовым шаблоном проекта CMake.

Компоненты

В этой статье будет взят контроллер STM32F303CC на плате RobotDyn STM32-MINI (black-pill). Разумеется, приведенный ниже код можно будет использовать и с другими контроллерами этой серии и платами.

Контроллер имеет 256 КБ флэш-памяти, 40 КБ SRAM и 8 КБ оперативной памяти CCM.

Что такое СММ?

В документации STM32F303 CCM память описывается следующим образом:

"Этот блок памяти используется для выполнения критических процедур или для доступа к данным. Доступ к нему может получить только процессор. DMA доступ не разрешен. Эта память адресуется на максимальной частоте ядра без циклов ожидания."

CCM SRAM тесно связана с ядром Arm® Cortex®, обеспечивая выполнение кода на максимальной тактовой частоте без каких-либо потерь на состояния ожидания. Это позволяет значительно сократить время выполнения критической задачи по сравнению с выполнением кода из флэш-памяти. CCM SRAM обычно используется в задачах реального времени и там, где требуются интенсивные вычисления, например:

  • цифровые контуры управления преобразователями (импульсные источники питания, освещение)
  • трехфазное управление двигателем
  • цифровая обработка сигналов в реальном времени

Если код находится в CCM SRAM, а данные хранятся в обычной SRAM, ядро Cortex-M4 будет соответствовать Гарвардской архитектуре. Выделенная память с нулевыми циклами ожидания подключена к каждой из шин I-Bus и D-Bus (см. рисунки ниже) и, таким образом, может работать на частоте 1,25 DMIPS/ МГц с детерминированной производительностью 90 DMIPS в STM32F3 и 213 DMIPS в STM32G4. Для обеспечения минимальной задержки подпрограммы обработки прерываний стоит также разместить в CCM SRAM.

Архитектура подключения оперативной памяти CCM типа следующая:

Архитектура подключения оперативной памяти CCMКак видите, CCM SRAM подключена только к I-bus (S0 <-> M3) и D-bus (S1 <-> M3).

Как с ней работать?

Так как же эту память использовать? Давайте клонируем следующий репозиторий:

https://bitbucket.org/dimtass/stm32f303-ccmram-test/src

Этот проект основан на шаблоне CMake, и он позволяет использовать CMM область памяти. По умолчанию CCM ОЗУ включена только в файле компоновщика "source/ config/ LinkerScripts/ STM32F303xC/ STM32F303VC_FLASH.ld", но автору также пришлось отредактировать файл "source/libs/cmsis/device/startup_stm32f30x. s" для того, чтобы реально иметь возможность использовать эту оперативную память:

  1. /* Copy the data segment initializers from flash to SRAM and CCMRAM */
  2. movs  r1, #0
  3. b  LoopCopyDataInit
  4.  
  5. CopyDataInit:
  6. ldr  r3, =_sidata
  7. ldr  r3, [r3, r1]
  8. str  r3, [r0, r1]
  9. adds  r1, r1, #4
  10.  
  11. LoopCopyDataInit:
  12. ldr  r0, =_sdata
  13. ldr  r3, =_edata
  14. adds  r2, r0, r1
  15. cmp  r2, r3
  16. bcc  CopyDataInit
  17. movs r1, #0
  18. b LoopCopyDataInit1
  19.  
  20. CopyDataInit1:
  21. ldr r3, =_siccmram
  22. ldr r3, [r3, r1]
  23. str r3, [r0, r1]
  24. adds r1, r1, #4
  25.  
  26. LoopCopyDataInit1:
  27. ldr r0, =_sccmram
  28. ldr r3, =_eccmram
  29. adds r2, r0, r1
  30. cmp r2, r3
  31. bcc CopyDataInit1
  32. ldr  r2, =_sbss
  33. b  LoopFillZerobss
  34. /* Zero fill the bss segment. */

Также в файле компоновщика вы можете увидеть определение области памяти и ее размера:

  1. MEMORY
  2. {
  3. FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 256K
  4. RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 40K
  5. MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
  6. CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 8K
  7. }

Видно, что область SRAM начинается с адреса 0x20000000 и имеет объем 40 КБ, а CCM RAM начинается с 0x10000000 и размером она 8 КБ. Важно помнить эти адреса при отладке вашего кода, вы сэкономите много времени, если будете знать, что ищете и чего можно ожидать.

В файл компоновщика также добавили .sram раздел, чтобы иметь возможность отображать функции в ОЗУ:

  1. /* Initialized data sections goes into RAM, load LMA copy after code */
  2. .data :
  3. {
  4. . = ALIGN(4);
  5. _sdata = .;        /* create a global symbol at data start */
  6. *(.data)           /* .data sections */
  7. *(.data*)          /* .data* sections */
  8. *(.sram)           /* .sram sections */
  9. *(.sram*)          /* .sram* sections */
  10.  
  11. . = ALIGN(4);
  12. _edata = .;        /* define a global symbol at data end */
  13. } >RAM AT> FLASH

Также в этом файле определен .ccmram раздел:

  1. .ccmram :
  2. {
  3. . = ALIGN(4);
  4. _sccmram = .;       /* create a global symbol at ccmram start */
  5. *(.ccmram)
  6. *(.ccmram*)
  7.  
  8. . = ALIGN(4);
  9. _eccmram = .;       /* create a global symbol at ccmram end */
  10. } >CCMRAM AT> FLASH

Чтобы протестировать CCMRAM, нужен код, который может нагрузить контроллер и оперативную память, и для этого было решено использовать библиотеку LZ4. Эта библиотека занимает очень мало места и написана на чистом C (поэтому она легко переносима). Из нее будет использоваться только одна функция для сжатия без последующей распаковки или проверки. (Поскольку тестируется производительность, оценка функциональности библиотеки не является критичной для этой задачи.)

Библиотека LZ4 находится в "source/libs/lz4".

В main.c работа библиотеки сжатия определяется следующим enum:

  1. enum {
  2. BLOCK_COUNT = USE_BLOCK_COUNT,
  3. BLOCK_SIZE = 1024 * USE_BLOCK_SIZE
  4. } ;

USE_BLOCK_COUNT и USE_BLOCK_SIZE определены в скрипте build.sh, который передает их в cmake. Значения по умолчанию:

  1. : $ {USE_CCM: = "ON" }
  2. : $ {USE_FLASH: = "OFF" }
  3. : $ {USE_SRAM: = "OFF" }
  4. : $ {USE_BLOCK_COUNT: = "512" }
  5. : $ {USE_BLOCK_SIZE: = "8" }

Переопределив значения при запуске скрипта, вы можете задать любой размер блока и их количество. Вот несколько примеров запуска:

  1. # 8K with 512 counts
  2. ./build.sh
  3. # It's the same with:
  4. USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 ./build.sh
  5. # 16K with 512 counts
  6. USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 ./build.sh

Соответственно, в этих случаях, процедура сжатия будет обрабатывать 512 * 1024 * 8 = 4 МБ  и 8 МБ данных. Конечно, на STM32F303CC нет 4 МБ или 8 МБ непрерывной памяти, поэтому автор использует USE_BLOCK_COUNT:

  1. inttest_lz4()
  2. {
  3. LZ4_stream_t lz4Stream_body;
  4. LZ4_stream_t* lz4Stream = &lz4Stream_body;
  5.  
  6. int  inpBufIndex = 0;
  7.  
  8. LZ4_initStream(lz4Stream, sizeof(*lz4Stream));
  9.  
  10. for(int i=0; i<BLOCK_COUNT; i++){
  11. char* const inpPtr = (char*)((uint32_t)0x20000000);
  12. constint inpBytes = BLOCK_SIZE;
  13. {
  14. char cmpBuf[LZ4_COMPRESSBOUND(BLOCK_SIZE)];
  15. constint cmpBytes = LZ4_compress_fast_continue(
  16. lz4Stream, inpPtr, cmpBuf, inpBytes, sizeof(cmpBuf), 1);
  17. if(cmpBytes <= 0){
  18. break;
  19. }
  20. }
  21. inpBufIndex = (inpBufIndex + 1) % 2;
  22. }
  23. return0;
  24. }

Как видно из приведенного выше кода, автор использует указатель inpPtr на область SRAM, которая начинается с 0x2000000, и затем код сжимает содержимое SRAM, используя заданный размер блока, который составляет 8K или 16K. Помните, что размер SRAM составляет 20 КБ, поэтому, если вы попытаетесь использовать размер блока больше 20 КБ, вызовется исключение MemManage_Handler()или HardFault_Handler() в "source/src/stm32f30x_it.c".

BLOCK_COUNT используется для цикла for, который считывает одну и ту же область SRAM несколько раз, поэтому 4 МБ и 8 МБ - это не последовательное хранилище, а скорее «кольцевой буфер», считываемый из SRAM. 

Наконец, тестовая функция вызывается каждую секунду следующим кодом:

  1. staticinlinevoidmain_loop(void)
  2. {
  3. /* 1 ms timer */
  4. if(glb_tmr_1ms){
  5. glb_tmr_1ms = 0;
  6. mod_timer_polling(&obj_timer_list);
  7. }
  8. if(glb_tmr_1000ms >= 1000){
  9. glb_tmr_1000ms = 0;
  10. glb_cntr = 0;
  11. DBG_PORT->ODR |= DBG_PIN;
  12. test_lz4();
  13. DBG_PORT->ODR &= ~DBG_PIN;
  14. TRACE(("lz4: %d\n", glb_cntr));
  15. }
  16. }

Glb_ * - переменные, которые увеличиваются каждую миллисекунду в функции обработки прерывания SysTick_Handler() в "source/src/stm32f30x_it.c". В объявлении функции был использован .ccmram атрибут, чтобы поместить обработчик прерываний в CCM RAM для более быстрого выполнения.

  1. __attribute__ ( ( section ( ".ccmram" ) ) )
  2. void SysTick_Handler ( void )
  3. {
  4. glb_tmr_1ms ++;
  5. glb_tmr_1000ms ++;
  6. glb_cntr ++;
  7. }

Этот же атрибут необходимо добавить к своим функциям, чтобы разместить их в CCM RAM области:

  1. __attribute__ ( ( section ( ".ccmram" ) ) )

Аналогичная строка может использоваться для помещения кода в SRAM:

  1. __attribute__ ( ( section ( ".sram" ) ) )

но с этим мы разберемся немного позже.

Теперь рассмотрим функцию LZ4_compress_fast_continue(). Если вы попытаетесь поместить эту функцию в CCM RAM, это не сработает, так как ее размер больше 8K. Но это и не нужно делать, достаточно поместить в эту область памяти функцию "LZ4_compress_generic ()", которая, собственно, и выполняет фактическое сжатие.

Определение функции "LZ4_compress_generic ()" в "source/libs/lz4/src/lz4.c" выглядит следующим образом:

  1. LZ4_FORCE_INLINE int LZ4_compress_generic ( ...

Строка "LZ4_FORCE_INLINE" предполагает, что функция будет встраиваться, а встроенные функции нельзя перемещать в CCMRAM или SRAM! Если вы просто используете следующий код, он не будет работать:

  1. __attribute__ ( ( section ( ".ccmram" ) ) )
  2. LZ4_FORCE_INLINE int LZ4_compress_generic (

Поэтому автору пришлось немного модифицировать эту библиотеку, добавив команды условной компиляции:

  1. #iffined (USE_CCM)
  2. __attribute__ ( ( section ( ".ccmram" ) ) )
  3. #eliffined (USE_FLASH)
  4. LZ4_FORCE_INLINE
  5. #eliffined (USE_SRAM)
  6. __attribute__ ( ( section ( ".sram" ) ) )
  7. #endif
  8. int LZ4_compress_generic ( ...

Как вы видите, флаг USE_CCM позволяет разместить функцию в области .ccmram. Флаг USE_FLASH задает обычное расположение функции во флэш - памяти. Наконец, флаг USE_SRAM помещает функцию в SRAM. Если все флаги отключены, то функция также будет помещена во флэш-память, но не будет встроена в вызывающую функцию.

Прежде чем перейти к тестам, давайте проверим, что флаги USE_ * действительно работают и каков результат. В Linux для этого можно использовать инструмент elfread и увидеть адрес любой функции.

Проверка флагов сборки и областей памяти

Прежде чем приступить к проверке, вспомним адреса областей памяти STM32F303CC.

Память

Начало

Конец

Размер (КБ)

ФЛЭШ

0x0800 0000

0x0803 FFFF

256

SRAM

0x2000 0000

0x2000 9FFF

40

CCMRAM

0x1000 0000

0x1000 1FFF

8

Теперь давайте сначала соберем код с помощью этой команды:

  1. USE_CCM = OFF USE_SRAM = OFF USE_FLASH = OFF ./build.sh

Конечно, все флаги писать не обязательно, так как они имеют значения по умолчанию, это сделано только для наглядности. Команда создаст файлы elf, hex и bin в папке "build-stm32/src/". Теперь вы можете использовать следующую команду для получения адреса функции LZ4_compress_generic:

  1. readelf -a build-stm32/src/stm32f303xc-ccm-test.elf | grep LZ4_compress_generic

Она вернет следующий результат:

  1. 363 : 0800158d 3482 FUNC GLOBAL DEFAULT     2 LZ4_compress_generic

Из ответа понятно, что адрес функции 0x0800 158d, это означает, что она находится в области флэш-памяти, и что функция не является встроенной.

Теперь выполним другую команду:

  1. USE_CCM = OFF USE_SRAM = OFF USE_FLASH = ON ./build. ш

и найдем адрес функции:

  1. readelf -a build-stm32/src/stm32f303xc-ccm-test.elf | grep LZ4_compress_generic

Функция не найдена, т.к.USE_FLASH=ON означает, что код функции встроен в функцию "LZ4_compress_fast_continue ()":

Последовательно проверяя результаты сборки с другими флагами, можно убедиться, что функция размещается в нужной области памяти:

  1. USE_CCM = OFF USE_SRAM = ON USE_FLASH = OFF ./build. ш
  2. readelf -a build-stm32/src/stm32f303xc-ccm-test.elf | grep LZ4_compress_generic
  3. 369 : 200000ed 3482 FUNC GLOBAL DEFAULT     6 LZ4_compress_generic

и:

  1. USE_CCM = ON USE_SRAM = OFF USE_FLASH = OFF ./build.sh
  2. readelf -a build-stm32/src/stm32f303xc-ccm-test.elf | grep LZ4_compress_generic
  3. 369 : 10000029 3482 FUNC GLOBAL DEFAULT 7 LZ4_compress_generic 

Быстродействие

Наконец, несколько замеров скорости! Теперь, когда мы убедились, что флаги работают, пришло время начать тестирование. Чтобы сделать его еще интереснее, сравним код сжатия на максимальной номинальной частоте ядра MCU 72 МГц, и когда он разогнан до 128 МГц.

Информация о скорости передается через UART, кроме того, ее можно получить с помощью осциллографа, наблюдая сигнал на заданном выводе микроконтроллера. Тесты запускаются следующим набором команд:

Flash-тесты (не встроенная функция)

  1. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  2. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  3.  
  4. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  5. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  6.  
  7. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  8. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  9.  
  10. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  11. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=OFF ./build.sh

 FLASH тесты (встроенная функция)

  1. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  2. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=ON ./build.sh
  3.  
  4. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  5. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=ON ./build.sh
  6.  
  7. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  8. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=ON ./build.sh
  9.  
  10. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  11. USE_CCM=OFF USE_SRAM=OFF USE_FLASH=ON ./build.sh

 Тесты SRAM

  1. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  2. USE_CCM=OFF USE_SRAM=ON USE_FLASH=OFF ./build.sh
  3.  
  4. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  5. USE_CCM=OFF USE_SRAM=ON USE_FLASH=OFF ./build.sh
  6.  
  7. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  8. USE_CCM=OFF USE_SRAM=ON USE_FLASH=OFF ./build.sh
  9.  
  10. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  11. USE_CCM=OFF USE_SRAM=ON USE_FLASH=OFF ./build.sh

 Тесты CCMRAM

  1. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  2. USE_CCM=ON USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  3.  
  4. USE_OVERCLOCKING=OFF USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  5. USE_CCM=ON USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  6.  
  7. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=8 USE_BLOCK_COUNT=512 \
  8. USE_CCM=ON USE_SRAM=OFF USE_FLASH=OFF ./build.sh
  9.  
  10. USE_OVERCLOCKING=ON USE_BLOCK_SIZE=16 USE_BLOCK_COUNT=512 \
  11. USE_CCM=ON USE_SRAM=OFF USE_FLASH=OFF

Результаты тестов приведены в расположенной ниже таблице ( в ней указано время выполнения функции test_lz4()в миллисекундах). Следовательно, чем меньше число, тем быстрее работает микроконтроллер.

 

FLASH
(не встроенный код)

FLASH
(встроенный код)

SRAM

CCMRAM

8K @ 72MHz

279

304

251

172

8K @ 128 МГц

156

171

141

97

16K @ 72 МГц

466

631

496

340

16K @ 128 МГц

262

355

278

191

Какие выводы можно сделать из таблицы?

  1. CCM быстрее по сравнению с любой другой памятью.
  2. SRAM оказывается не намного быстрее по сравнению с Flash.
  3. Принудительное включение опции компилятора (LZ4_FORCE_INLINE) приводит к ухудшению производительности для блоков обоих размеров!
  4. При размере блока 16 КБ, размещенный во FLASH код работает немного быстрее, чем SRAM!

Следовательно, CCM является самым быстрым ОЗУ, которое вы можете использовать в контроллере STM32F303CC. Жаль только, что его размер всего 8К.

Выводы

Автор статьи случайно обнаружил этот тип памяти CCM в даташите и решил протестировать ее быстродействие. Он, конечно, ожидал, что она будет немного быстрее, но не ожидал, что разница будет такой большой. 31% - это огромный выигрыш в производительности, особенно в случае критичного ко времени выполнения кода.

То, что флэш будет быстрее, чем ОЗУ, неожиданно, но автор предполагает, что это происходит потому, что он использует ОЗУ в качестве входных данных для функции сжатия, и когда размер блока составляет 16 КБ (что составляет почти весь объем ОЗУ), то оказывается, что это замедляет скорость чтения/записи. Это теория автора, и не факт, что она правильная. Но, в любом случае, с большими блоками STM32 работает лучше, если функция сжатия выполняется из флэш-памяти.

И, наконец, LZ4_FORCE_INLINE в LZ4_compress_generic(), похоже, ухудшает производительность в рассматриваемом случае.

Ну и, конечно, вы можете использовать шаблон cmake для контроллера STM32F303CC, и с помощью директивы __attribute__  размещать функции как в области .ccmram, так и в .sram.

Источник: https://www.stupid-projects.com

Производитель: STMicroelectronics
Наименование
Производитель
Описание Корпус/
Изображение
Цена, руб. Наличие
STM32F303CCT6
STM32F303CCT6
STMicroelectronics
Арт.: 1048153 ИНФО PDF AN RD DT
Доступно: 1959 шт. 188,00
Analog and DSC with FPU ARM Cortex-M4 MCU with 128 Kbytes Flash, 72 MHz CPU, MPU, CCM, 12-bit ADC 5Msps, PGA, comparators
STM32F303CCT6 188,00 от 13 шт. 174,00 от 44 шт. 168,00 от 92 шт. 160,00
1957 шт.
(на складе)
2 шт.
(под заказ)
STM32F303CCT6TR
STM32F303CCT6TR
STMicroelectronics
Арт.: 1318224 ИНФО PDF AN RD DT
Доступно: 2134 шт. от 2400 шт. от 172,39
Выбрать
условия
поставки
ARM Microcontrollers - MCU 16/32-BITS MICROS
STM32F303CCT6TR от 2400 шт. от 172,39
2134 шт.
(под заказ)
Выбрать
условия
поставки
STM32F303CCT7
STM32F303CCT7
STMicroelectronics
Арт.: 1899814 ИНФО PDF AN RD DT
Доступно: 1283 шт. от 1500 шт. от 286,69
Выбрать
условия
поставки
ARM Microcontrollers - MCU 16/32-BITS MICROS
STM32F303CCT7 от 1500 шт. от 286,69
1283 шт.
(под заказ)
Выбрать
условия
поставки

Сравнение позиций

  • ()