Что вообще может DMA?
Сначала рассмотрим схему и табличку из документации, чтобы понять, как настроить DMA и какие у него есть возможности. У DMA контроллера — 7 каналов. У микроконтроллеров STM32 могут быть несколько DMA контроллеров. Конечно, они могут работать параллельно. В нашем STM32F103C8 только один 7-канальный DMA контроллер.
Из схемы и таблицы видно, что определенная периферия закреплена за определенными каналами. И, если мы используем ADC1, то мы его можем использовать только на канале 1. У нас нет возможности перенести ADC1 на канал, который нам заблагорассудится. Итак, если мы используем первый канал под ADC1, тогда TIM2_CH3 и TIM4_CH1 (согласно таблице) — в пролете. То есть, в DMA мы их уже не сможем задействовать. Для работы с UART1 нам понадобится каналы 4, 5
Обратите внимание, что, скажем, ADC2 вообще здесь не фигурирует. То есть, не любую периферию можно использовать с DMA
Комбинаций, как видим, не так много и надо все четко планировать, чтобы каналы DMA были заняты исключительно по делу. Также следует понимать, что в DMA все же есть ограничения по скорости.
How it works
The structure type defined in holds the required variables for the DMA timeout implementation. The DMA buffer size and timeout duration can be configured in . When a UART idle interrupt occurs, the timer is set to the configured duration and decreased in the SysTick interrupt handler. After timeout, the flag is set and the DMA transfer complete callback is executed. The prevCNDTR stores the previous value of the DMA CNDTR register value, thus only the relevant, newly received data chunk can be extracted and processed from the DMA buffer.
When a DMA transfer complete interrupt or DMA timeout occurs, the DMA transfer complete callback is executed. Based on timeout state; current and previous state of DMA (stored in the structure), the newly received data (which can be the entire DMA buffer or only a part of it) is copied from the DMA buffer to a new buffer. Then the data can be processed without being corrupted or overwritten by further incoming data. In this demonstration the received data is simply forwarded back to the computer via USB.
Calculations
Calculation of USART_DIV and Mantissa for configuration
Based in the equation found on the data sheet,
Constant value: BaudRate = 115200 OVER8 = // OVER8 = 1 if oversampling by 8 Freq = 45MHz // Clock Frequency USART_DIV = Fclk / USART_DIV = 45000000 / USART_DIV = 24.41 Mantissa = 24 Fractional part = 0.41* 16(shift left by 4-bits(*2^4) for 16-bits oversampling, 8-bits oversampling shift left by 3-bits(*2^3)) Fractional part = 6.56 = 6
Calculation of counter value to allow LED blink 4 timers per second
Blink 4 times per second, One cycle of on and off equals to one blink. Hence, the LED will be toggle on/off for 8 times in a second. Blink period = 1 / 8 = 125ms Since timer 2 is located at APB1 Bus, the clock frequency of timer2 is 90MHz. timer2 period = 1 / [90M / (pre-scaler + 1)] ; pre-scaler is selected as 29 to ease calculation. = 1 / (90M / 30) = 3.3333 x 10^-7 s timer2 counter value = Blink period (desired) / timer2 period = 125ms / 3.3333 x 10^-7 s = 375004 # 375004 will be loaded into ARR of timer2, when the counter reaches this value. It will generate an interrupt.
Examples for UART DMA for TX (and optionally included RX)
- Application is using DMA in normal mode to transfer data
- Application is always using ringbuffer between high-level write and low-level transmit operation
- DMA TC interrupt is triggered when transfer has finished. Application can then send more data
Demo application for debug messages
This is a demo application available in folder.
Its purpose is to show how can application implement output of debug messages without drastically affect CPU performance.
It is using DMA to transfer data (no CPU to wait for UART flags) and can achieve very high or very low data rates
- All debug messages from application are written to intermediate ringbuffer
- Application will try to start & configure DMA after every successfive write to ringbuffer
- If transfer is on-going, next start is configured from DMA TC interrupt
As a result of this demo application for STM32F413-Nucleo board, observations are as following:
- Demo code sends bytes every second at bauds, which is approx .
- With DMA disabled, CPU load was , in-line with time to transmit the data
- With DMA enabled, CPU load was
- DMA can be enabled/disabled with macro configuration in
Code-Snippet for STM32
When you generate the code in STM32, the main.c file will have the following structure. You will need to add code in whichever section as per requirements.
/*License Declaration*/// Header File#include "main.h"// User Defined Header// Global Variable Declaration (Section 1)// Peripheral Initialization -> Handled by CubeMXint main(){ // Program Initialization (Section 2) while(1){ // Looping Code (Section 3) } // Handle End of Run (Section 4)}
This the general template you will see when the code is generated. The variables declared in Section 1, will act as global variables. Now, we can sub-divide the project into the following tasks:
- Create Structure (I will make one for collection of accelerometer data since STM32F4 has one builtin. You can program that and use the actual data but for this tutorial I will generate the data).
- Make a Random Data Generator Function which returns a number within a given range. (Similar to the data a sensor might return)
- Create a function to handle UART Transmission
Define Structure
(Section 1)/* * @brief Structure to hold and receive accelerometer data * @param x,y,z axes data * @size float -> 4B => x,y,z = 12B per instance of struct */struct XL_Data{ float x,y,z}tx_data;
Random-Data-Generator function
(Section 1)/** * @brief Function to Generate Random Data * @param float min, float max */float genXLvals(float min, float max){ return ((rand() % ((max-min+1)) + min);}
This function will return a value which lies in between the min and max values.
UART and HAL Libraries in STM32
The UART operation is a very complex procedure, no matter which micro-controller you are using. Fortunately, STM32 has libraries called LL and HAL. HAL stands for High Abstraction Layer and as the name implies, these libraries handle the entire bit-wise operations and the user only has to pass the right data to the functions.
When we select USART in CubeMX, it will add the corresponding HAL library to our projects and we can directly call those function from our code.
(Section 1)/* * @brief Function to convert and send data over UART * @param XL_Data, UART Handle Typedef */void sendData(struct XL_Data * data, UART_HandleTypeDef * huart){// UART can only send unsigned int // Thus we need to convert our struct data char buffer; // Create a char buffer of right size// Copy the data to buffer memcpy(buffer, &data, sizeof(data)); // Copy and convert the data // Ideally buffer will be 12B long, 4B for each axes data // Now we can finally send this data HAL_UART_Transmit(huart, (uint8_t *)buffer, sizeof(buffer), 50);// The last param is timeout duration in ms}
The Main Code
Now that we have all our basic functions covered, we need to setup the while loop to send the data continuously.
(Section 3)// Indicate Start of Sequence with "S"HAL_UART_Transmit(&huart, (uint8_t*)"S", sizeof("S"), 50);// Populate the Accelerometer Structuretx_data.x = genXLvals(0, 1024);tx_data.y = genXLvals(0, 1024);tx_data.z = genXLvals(0, 1024);// Send the Data over UARTsendData(&tx_data, &huart2); // huart2 is auto-generated for USART2// Indicate End of Sequence with "Z"HAL_UART_Transmit(&huart, (uint8_t*)"Z", sizeof("Z"), 50);HAL_Delay(100); // Delay for 100ms
Обработка ошибок на уровне пакетов и таймаутов
- Если код команды не соответствует проверочному инверсному коду, то начало пакета не засчитывается, и поиск новой команды начинается со следующих байт.
- Если принят пакет с размером дополнительной информации больше чем внутренний буфер, то пакет игнорируется, и поиск новой команды начинается со следующих после размера байт.
- Если принятая в теле пакета CRC32 не равна фактически расчётной, то содержимое пакета игнорируется и поиск новой команды начинается с следующих после CRC32 байт.
- Если приходят байты, но сигнатура начала пакета не задетектирована. То эти байты считать отладочными текстовыми сообщениями и накапливать до кода 13 (перевод строки), а после этого кода выводить в отладочную консоль.
- Если с момента приёма последнего пакета прошло более 500мс, то загрузчик сбрасывается в изначальное состояние. Пакет, который не успел приняться до конца, игнорируется и так-же сбрасывается. О таймауте Устройство сообщает пакетом с специальным кодом команды «таймаут».
- При запуске загрузчика генерируется другой пакет со специальным кодом «перезагрузка».
Прошивка STM32 с помощью USB-Uart переходника под Linux (Ubuntu)
Устанавливаем stm32flash
http://launchpadlibrarian.net/188294676/stm32flash_0.4-2_i386.debstm32flashhttps://launchpad.net/ubuntu/wily/i386/stm32flash/0.4-2
Если используем USB-UART переходник, имя порта буде примерно такое /dev/ttyUSB0
Получить информацию о чипе
Результат:
stm32flash 0.4 http://stm32flash.googlecode.com/ Interface serial_posix: 57600 8E1 Version : 0x22 Option 1 : 0x00 Option 2 : 0x00 Device ID : 0x0410 (Medium-density) - RAM : 20KiB (512b reserved by bootloader) - Flash : 128KiB (sector size: 4x1024) - Option RAM : 16b - System RAM : 2KiB
Пишем в чип
Результат:
stm32flash 0.4 http://stm32flash.googlecode.com/ Using Parser : Raw BINARY Interface serial_posix: 57600 8E1 Version : 0x22 Option 1 : 0x00 Option 2 : 0x00 Device ID : 0x0410 (Medium-density) - RAM : 20KiB (512b reserved by bootloader) - Flash : 128KiB (sector size: 4x1024) - Option RAM : 16b - System RAM : 2KiB Write to memory Erasing memory Wrote and verified address 0x08012900 (100.00%) Done. Starting execution at address 0x08000000... done.
1.5 RS422/485 интерфейс
Установленные на плате микросхемы приемопередатчики ADM2682E отвечают за преобразование уровней встроенного в микроконтроллер приемопередатчика USART в уровни сигналов интерфейса RS422/485.
Изделия ADM2682E/ADM2687E фирмы Analog Devices представляют собой полностью интегральные приёмопередатчики данных с эффективным 5 кВ сигналом и изолированным питанием, имеющие ±15 кВ ESD защиту и пригодные для сверхскоростной связи на многопунктовых линиях передачи.
Каждое из изделий ADM2682E/ADM2687E содержит интегральный изолированный DC-DC преобразователь. В приборах интегрирована технология iCoupler фирмы Analog Devices, Inc., сочетающая в одном корпусе 3-канальный вентиль, трёхступенчатый дифференциальный драйвер линии, дифференциальный входной приёмник и разработанный фирмой Analog Devices преобразователь постоянного напряжения isoPower. Микросхема питаются от одиночного источника питания 5 В или 3.3 В, производящего полностью интегральный сигнал, представляя собой RS-422/485 решение с развязкой по цепям питания.
Характерные особенности и преимущества
- Изолированный RS-485/RS-422 приёмопередатчик с эффективным 5 кВ сигналом, с конфигурацией в виде полудуплексной или дуплексной связи;
- Интегральный изолированный преобразователь постоянного напряжения isoPower ;
- ±15 кВ ESD защита на выводах RS-485 вход/выход;
- Соответствует требованиям стандартов ANSI/TIA/EIA-485-A-98 и ISO 8482:1987(E);
- Питание от 5 В или 3.3 В;
- Соединяет до 256 узлов на одной шине;
- Входы приёмника предохранены от размыкания и КЗ и отказов;
- Высокая стойкость к переходным процессам в синфазном режиме: >25 кВ/мксек;
- Скорость передачи данных: 500 кБит/сек для ADM2682E до 16 Mбит/сек для ADM2687E.
Назначение контактов разъема ХP8 (XP9)
1 RS422/485_TX1+
2 RS422/485_TX1-
3 RS422/485_GND
4 RS422/485_RX1-
5 RS422/485_RX1+
6 RS422/485_GND
7 RS422/485GND
8 RS422/485+5V
9 RS422/485_GND
Структурная схема подключения 4-проводного RS422 – интерфейса.
Установкой перемычек подключаются согласующие резисторы. Для подключения к цепям RX+ и RX- каналов XP11 и XP12 согласующего резистора установить перемычки на X34 и X32 в соответствии с рисунком ниже.
Для реализации 2-проводного RS485 интерфейса необходимо соединить линии RX- и TX- и линии RX+ и TX+ так, как показано на рисунке ниже. Для этого соединить в кабельном разъеме контакты 1,5 и 2, 4.
Первая программа
CooCox
Выполняем пункт меню Project -> New
Указываем имя проекта:
Выбираем Чип:
В репозитории выбираем какие именно модули мы будем использовать:
Открываем main.c і набираем следующий код программы:
Компилируем (Project->Build)
При первой компиляции IDE может запросить указать местонахождение компилятора.
Надо корректно указать место, куда был установлен GCC.
После удачной компиляции заливаем программу в микроконтроллер. Эта программа будет мигать светодиодом на плате. Как залить программу в микроконтроллер мы рассматривали в предыдущей статье.
Если Вы будете заливать прошивку через UART с помощью UART-USB переходника, файл для заливки найдете в директории:
C:\CooCox\CoIDE\workspace\Example_GPIO\Example_GPIO\Debug\bin\Example_GPIO.bin
Если у Вас есть установленный ST-Link программатор, программу в микроконтроллер можно залить прямо с IDE (Flash -> Program Download).
Если при этом возникла ошибка «Error: Flash driver function execute error» Рекомендуется:
- Запустить STM32 ST-LINK Utility и выполнить Frimware update.
-
Скопировать файл STLinkUSBDriver.dll из папки
C:\Program Files\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility
в папку
C:\CooCox\CoIDE\bin
после чего перезапустить CooCox IDE
Регистры USART
Status register (USART_SR) — регистр статуса
TXE: регистр передатчика пуст. Этот бит устанавливается аппаратно, когда содержимое регистра передатчика TDR (TDR не доступен напрямую из программы, но туда попадают данные при записи в USART_DR) было передано в сдвиговой регистр. Если в USART_CR1 был установлен бит разрешения прерывания TXEIE, то в этот момент генерируется запрос прерывания USART. TXE сбрасывается при записи значения в регистр данных USART_DR.
TC: передача завершена. Этот бит устанавливается аппаратно, если UART завершил передачу данных, при этом бит TXE установлен в единицу. Этот бит может быть полезен для реализации интерфейса RS485 для переключения направления драйвера RS485. Если в регистре USART_CR1 установлен бит TCIE, то генерируется прерывание USART при установке бита TC. Бит TC сбрасывается следующей программной последовательностью: чтение регистра USART_SR с последующей записью в регистр USART_DR. Кроме того, бит TC можно сбросить записью в него значения 0, но это рекомендуется производить только в режиме совместной работы с DMA.
RXNE: регистр приемника не пуст. Этот бит устанавливается в единицу, когда содержимое сдвигового регистра приемника передается в регистр данных USART. Если в регистре USART_CR1 установлен бит RXNEIE, то генерируется запрос прерывания USART. Бит RXNE сбрасывается при чтении регистр данных USART_DR. Кроме того, RXNE можно сбросить записью в него значение 0, но это рекомендуется производить только в режиме совместной работы с DMA.
ORE: ошибка переполнения. Устанавливается в 1, если данные в сдвиговом регистре приемника готовы к передаче в регистр данных, но при этом установлен бит RXNE. Иными словами, мы уже получили очередной байт по USART, но еще не прочитали предыдущий. Если в регистре USART_CR1 установлен флаг RXNEIE, то генерируется запрос прерывания USART. Бит ORE сбрасывается следующей программной последовательностью: чтение регистра USART_SR с последующим чтением регистра USART_DR.
Data register (USART_DR) — регистр данных
DR: данные. Этот регистр содержит 2 теневых регистра: TDR и RDR. При чтении из DR будет прочитано значение регистра данных приемника RDR, при записи в DR значение будет записано в регистр данных передатчика TDR. Если используется контроль четности (бит PCE в регистре USART_CR1 установлен в 1), то при записи в DR значение старшего бита будет игнорироваться, так как при передаче он будет заменен битом четности. При приеме с включенным контролем четности старший бит будет содержать бит четности.
Baud rate register (USART_BRR) — регистр скорости передачи данных USART
Регистр BRR содержит коэффициент деления, который задает скорость передачи данных по USART.
BRR = (uint16_t)(BUS_FREQ / BAUD)
где BUS_FREQ — частота шины, на которой висит данный USART
BAUD — желаемая скорость передачи данных.
Control register 1 (USART_CR1) — регистр конфигурации 1
UE: включить USART.
- 0: предделители USART и его выходы отключены
- 1: USART включен
M: длина слова данных. Этот бит определяет длину передаваемых данных. Устанавливается и очищается программно.
- 0: 1 старт-бит, 8 бит данных, n стоп-бит
- 1: 1 старт-бит, 9 бит данных, n стоп-бит
PCE: разрешить контроль четности. Устанавливается и очищается программно
PS: выбор типа контроля четности. Этот бит выбирает вариант контроля четности, если установлен бит PCE. Устанавливается и очищается программно.
- 0: Even
- 1: Odd
TXEIE: разрешить прерывание при опустошении буфера передатчика. Если установлен в 1, то генерируется запрос прерывания USART при установке бита TXE регистра USART_SR.
TCIE: разрешить прерывания окончания передачи. Если 1, то генерируется запрос прерывания USART при установке флага TC в регистре USART_SR.
RXNEIE: разрешить прерывание при появлении данных в регистре приемника. Если 1, то генерируется запрос прерывания USART при установке флага RXNE или ORE в регистре USART_SR.
TE: включить передатчик USART
RE: включить приемник USART
Control register 2 (USART_CR2) — регистр конфигурации 2
STOP: количество STOP-битов
- 00: 1 стоп-бит
- 01: 0.5 стоп-бита
- 10: 2 стоп-бита
- 11: 1.5 стоп-бита
0.5 и 1.5 стоп-бита не доступны для UART4 и UART5 (UART4 и UART5 отсутствуют в микроконтроллере STM32F103C8)
Порядок работы со стороны хоста
- Получаем информацию с устройства: размер флеша, ChipID, размер буфера приёма, адрес старта, версия загрузчика и чипа.
- Даём команду стирания либо всей прошивки, либо части указав размер стираемой области.
- По завершению стирания начинаем непрерывно отправлять команды записи блоками друг за другом, проверяя что текущая записанная позиция в устройстве увеличивается.
- Если позиция перестала увеличиваться (приняли два ответа на команду «запись» с одинаковым адресом), то скорректировать адрес на Хосте, сбросить буферы отправки и начать передавать с нового скорректированного адреса.
- По завершению записи подать команду «Старт» передав CRC32 всей прошивки, в ответ Устройство сообщит фактическое CRC32.
- Если фактическое CRC32 равно расчётному, то загрузка успешно закончена и прошивка запущена.
Настройка порта GPIO
Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:
На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World!
Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:
Рис. 8. Таблица шин и периферийных устройств
GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:
Рис. 9. Регистр RCC_APB2ENR
В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.
Поехали программировать! За основу возьмем проект из 2-й части: https://github.com/DiMoonElec/stm32f103c8_empty_project. Создадим функцию инициализации порта:
void PortInit(void) { }
GPIOB
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB
PB12CRH:
GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль //MODE: выход с максимальной частотой 2 МГц //CNF: режим push-pull GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
void PortInit(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль //MODE: выход с максимальной частотой 2 МГц //CNF: режим push-pull GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos); }
ODRPB12
void PortSetHi(void) { GPIOB->ODR |= (1<<12); }
void PortSetLow(void) { GPIOB->ODR &= ~(1<<12); }
GPIOB->ODR |= (1<<12)STM32
void PortSetHi(void) { GPIOB->BSRR = (1<<12); } void PortSetLow(void) { GPIOB->BRR = (1<<12); }
(1<<12)0x1000BSRRBRRрис. 5, 6
Ни и простой main() для проверки:
void main() { int i; PortInit(); for(;;) { PortSetHi(); for(i=0; i<0x40000; i++) ; PortSetLow(); for(i=0; i<0x40000; i++) ; } }
BP12«Hello, World!»
Давайте теперь настроим какой-нибудь вывод порта, например PB15, на вход с подтяжкой к питанию. При подключении PB15 к минусу, у нас будет зажигаться светодиод. Задача ясна, преступаем к реализации. В PortInit() добавим пару строк:
/// Настраиваем PB15 на вход с подтяжкой к питанию /// GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15); //MODE: вход, оставляем в нуле //CNF: вход с pull-up / pull-down GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos); GPIOB->ODR |= (1<<15); //Включаем подтяжку вверх
PB12MODE/CNFODRODRCNF=10Input with pull-up / pull-downODRReference manual
Рис. 10. Таблица конфигурации порта
Функция PortInit() приобретает такой вид:
void PortInit(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Включаем тактирование порта GPIOB /// Настраиваем PB12 на выход /// GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //для начала все сбрасываем в ноль //MODE: выход с максимальной частотой 2 МГц //CNF: режим push-pull GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos); /// Настраиваем PB15 на вход с подтяжкой к питанию /// GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15); //MODE: вход, оставляем в нуле //CNF: вход с pull-up / pull-down GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos); GPIOB->ODR |= (1<<15); //Включаем подтяжку вверх }
PB15IDR
int ReadPort(void) { if(GPIOB->IDR & (1<<15)) return 1; return 0; }
IDR
В этом случае main() будет выглядеть вот так:
void main() { PortInit(); for(;;) { if(ReadPort()) PortSetHi(); else PortSetLow(); } }
stm32f103c8PB12PB15PP12PB15PB15ReadPort()PB12
На этом все, продолжение следует! Продолжение.
1.3 CAN интерфейс
Установленные на плате микросхемы SN65HVD230 отвечают за преобразование уровней встроенного в микроконтроллер приемопередатчика шины CAN. Структурная схема подключения CAN интерфейса отображена на рисунке ниже. Для подключения согласующего резистора R устанавливается перемычка S.
На рисунке ниже показано направление нумерации выводов разъема XT3 и XT4. Для подключения согласующих резисторов устанавливаются перемычки на штыри X15, X16.
Установкой перемычек на штыри X13 и X14 определяет режим работы микросхемы CAN – трансивера.
Если перемычка установлена так, как показано на рисунке:
используется высокоскоростной режим работы.
Если установлена:
микросхема работает в режиме хранения.
USB Mass Storage пример
STM32_USB-FS-Device_Lib_V4.0.0.о USB и использовании STM32_USB-FS-Device_Libhttps://github.com/avislab/STM32F103/tree/master/Example_USB_Mass_Storagemass_mal.h
Давайте покопаемся в коде и разберемся как выполняется работа с Mass Storage. Оказывается микроконтроллер вообще ничего не знает о FAT, кластерах и так далее. Он работает только как посредник — передает данные с USB во Flash, и с Flash в USB. А куда и что писать, собственно, решает операционная система.
Отдавать 20Кб флэш памяти микроконтроллера для FAT — это многовато. А что вообще можно придумать для реализации Bootloader-а в режиме USB Mass Storage? Фактически нам нужно выполнять только одну операцию — копирование одного файла. Можно даже с фиксированным именем. Можно обманывать операционную систему и программно формировать данные FAT, но фактически не хранить ее. Для одного файла в корне с фиксированным именем это вполне реальная идея для экономии памяти. При копировании файла на наш USB Mass Storage надо будет лишь записать данные во флэш. Но я прикинул сколько придется углубляться в «муляж» FAT12 и сколько выгребать «граблей», что решил пока отложить этот замысел. Решим задачу более простым путем.
Я не нашел в Интернете чтобы так кто-то делал, поэтому говорю что это моя собственная идея. Мне приятно так думать, но у меня есть подозрение что я не первый такой хитрый 🙂
Настройка DMA
DMAInitTypeDefstm32f10x_dma.h
DMA_PeripheralBaseAddr – адрес периферийного устройства
DMA_MemoryBaseAddr – адрес памяти
DMA_DIR – напрвыление передачи. Данные могут передаваться с периферии в память и наоборот, из памяти в периферию (DMA_DIR_PeripheralDST | DMA_DIR_PeripheralSRC)
DMA_BufferSize – размер буфера даних
DMA_PeripheralInc — указывает надо ли инкрементировать адреса данных в периферии (DMA_PeripheralInc_Enable | DMA_PeripheralInc_Disable)
DMA_MemoryInc – указывает надо ли инкрементировать адреса данных в памяти (DMA_MemoryInc_Enable | DMA_MemoryInc_Disable)
Если вернуться к примеру работы АЦП через DMA, то мы увидим, что в настройках DMA
DMA_MemoryInc = Enable
DMA_PeripheralInc = Disable
Это потому, что мы раскладываем данные АЦП в массив и нам нужно включить инкрементация адресов в памяти. Чтобы данные из разных каналов записывались на свои места. А выходной регистр в АЦП один, и нам следует выключить инкрементация на периферии.
DMA_PeripheralDataSize – размер единицы данных для переферии
DMA_MemoryDataSize – размер единицы данных для памяти
Эти поля могут принимать следующие значения:
DMA_PeripheralDataSize_Byte
DMA_PeripheralDataSize_HalfWord
DMA_PeripheralDataSize_Word
DMA_MemoryDataSize_Byte
DMA_MemoryDataSize_HalfWord
DMA_MemoryDataSize_Word
DMA_Mode – режим работи канала DMA (DMA_Mode_Circular | DMA_Mode_Normal)
DMA_Priority – приоритет канала DMA (DMA_Priority_VeryHigh | DMA_Priority_High | DMA_Priority_Medium | DMA_Priority_Low)
DMA_M2M – передача память > память (DMA_M2M_Enable | DMA_M2M_Disable)
Регистры в CMSIS
Во второй части мы учились подключать библиотеку CMSIS к IAR-у, сейчас нам понадобится этот проект, так как мы переходим к практике. Но перед этим немного поговорим о том, как устроено обращение к регистрам периферии в CMSIS.
Каждый экземпляр периферии является структурой, в которой находятся все регистры, относящиеся к данному устройству. Почти во всех случаях имя структуры совпадает с именем периферийного модуля. Для микроконтроллера STM32F103C8 все структуры периферийных модулей объявлены в файле stm32f103xb.h:
#define TIM2 ((TIM_TypeDef *)TIM2_BASE) #define TIM3 ((TIM_TypeDef *)TIM3_BASE) #define TIM4 ((TIM_TypeDef *)TIM4_BASE) #define RTC ((RTC_TypeDef *)RTC_BASE) #define WWDG ((WWDG_TypeDef *)WWDG_BASE) #define IWDG ((IWDG_TypeDef *)IWDG_BASE) #define SPI2 ((SPI_TypeDef *)SPI2_BASE) #define USART2 ((USART_TypeDef *)USART2_BASE) #define USART3 ((USART_TypeDef *)USART3_BASE) #define I2C1 ((I2C_TypeDef *)I2C1_BASE) #define I2C2 ((I2C_TypeDef *)I2C2_BASE) #define USB ((USB_TypeDef *)USB_BASE) #define CAN1 ((CAN_TypeDef *)CAN1_BASE) #define BKP ((BKP_TypeDef *)BKP_BASE) #define PWR ((PWR_TypeDef *)PWR_BASE) #define AFIO ((AFIO_TypeDef *)AFIO_BASE) #define EXTI ((EXTI_TypeDef *)EXTI_BASE) #define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) #define GPIOB ((GPIO_TypeDef *)GPIOB_BASE) #define GPIOC ((GPIO_TypeDef *)GPIOC_BASE) #define GPIOD ((GPIO_TypeDef *)GPIOD_BASE) #define GPIOE ((GPIO_TypeDef *)GPIOE_BASE) #define ADC1 ((ADC_TypeDef *)ADC1_BASE) #define ADC2 ((ADC_TypeDef *)ADC2_BASE) #define ADC12_COMMON ((ADC_Common_TypeDef *)ADC1_BASE) #define TIM1 ((TIM_TypeDef *)TIM1_BASE) #define SPI1 ((SPI_TypeDef *)SPI1_BASE) #define USART1 ((USART_TypeDef *)USART1_BASE) #define SDIO ((SDIO_TypeDef *)SDIO_BASE) #define DMA1 ((DMA_TypeDef *)DMA1_BASE) #define DMA1_Channel1 ((DMA_Channel_TypeDef *)DMA1_Channel1_BASE) #define DMA1_Channel2 ((DMA_Channel_TypeDef *)DMA1_Channel2_BASE) #define DMA1_Channel3 ((DMA_Channel_TypeDef *)DMA1_Channel3_BASE) #define DMA1_Channel4 ((DMA_Channel_TypeDef *)DMA1_Channel4_BASE) #define DMA1_Channel5 ((DMA_Channel_TypeDef *)DMA1_Channel5_BASE) #define DMA1_Channel6 ((DMA_Channel_TypeDef *)DMA1_Channel6_BASE) #define DMA1_Channel7 ((DMA_Channel_TypeDef *)DMA1_Channel7_BASE) #define RCC ((RCC_TypeDef *)RCC_BASE) #define CRC ((CRC_TypeDef *)CRC_BASE) #define FLASH ((FLASH_TypeDef *)FLASH_R_BASE) #define OB ((OB_TypeDef *)OB_BASE) #define DBGMCU ((DBGMCU_TypeDef *)DBGMCU_BASE)
RCC_CRHSEON
RCC->CR |= RCC_CR_HSEON_Msk;
RCC->CR |= (1 << RCC_CR_HSEON_Pos);
Сначала идет имя периферийного модуля, в нашем случае «RCC». Затем символ «->», после чего имя регистра «CR». RCC_CR_HSEON_Msk представляет собой вот такой #define:
#define RCC_CR_HSEON_Msk (1<<16)
HSEONCRRCC_CR_HSEON_Msk_MskRCC_CR_HSEON_Msk
#define RCC_CR_HSEON RCC_CR_HSEON_Msk
_Msk.
Второй случай выглядит аналогичным образом:
где
#define RCC_CR_HSEON_Pos 16
RCC_CR_HSEON_Pos_Pos
А как быть с параметрами, которые имеют несколько битов? К примеру в регистре CFGR мы хотим установить значение множителя PLL равное девяти, имеющее код 0111 (см. рис. 2 биты PLLMUL). Тут вот такое решение:
RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2; RCC->CFGR &= ~(RCC_CFGR_PLLMULL_3);
PLLMULPLLMUL
RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;
RCC->CFGR |= (7 << RCC_CFGR_PLLMULL_Pos);
PLLMUL
Examples for UART DMA for TX (and optionally included RX)
- Application is using DMA in normal mode to transfer data
- Application is always using ringbuffer between high-level write and low-level transmit operation
- DMA TC interrupt is triggered when transfer has finished. Application can then send more data
Demo application for debug messages
This is a demo application available in folder.
Its purpose is to show how can application implement output of debug messages without drastically affect CPU performance.
It is using DMA to transfer data (no CPU to wait for UART flags) and can achieve very high or very low data rates
- All debug messages from application are written to intermediate ringbuffer
- Application will try to start & configure DMA after every successfive write to ringbuffer
- If transfer is on-going, next start is configured from DMA TC interrupt
As a result of this demo application for STm32F413-Nucleo board, observations are as following:
- Demo code sends bytes every second at bauds, which is approx .
- With DMA disabled, CPU load was , in-line with time to transmit the data
- With DMA enabled, CPU load was
- DMA can be enabled/disabled with macro configuration in
- run to clone repository including submodules
- run examples from directory using Atollic TrueSTUDIO
Заключение
В общем и целом перед поставленной задачей товарищи студенты справились.
При включенном принудительном inline, весь код проекта занимает 1600 байт без оптимизации. Сделаны две команды: запись и чтение 12 параметров во Flash микроконтроллера. В проекте, можно настроить драйвер, чтобы он работал в синхронном режиме, можно в асинхронном. Можно подключать любое количество подписчиков, к любому UART. Собственно все задачи были выполнены и работа тянет на отлично 🙂
Да, затрачено на кодирование было 2 целых дня (думаю часов 20 в сумме). Полагаю, из-за того, что архитектура мною уже была разработана на практических занятиях, а реализация — это дело уже не таке сложное.
Код был проверен мною в PVS-Studio. Изначально были найдены 4 предупреждения.
Все предупреждения уже не помню, отчет не сохранил: но точно были V2516 и V519, ошибки не критичные, но точно так делать не надо было 🙂 Все исправлено, кроме V2516, он указывает на код, который используется для отладки, там поставил FIXME:.