Подключение stm32 к компьютеру по uart
char Soft_UART_Init(unsigned long *port, unsigned long rx, unsigned long tx, unsigned long baud_rate, unsigned int inverted);
Configures and initializes the software UART module.
Software UART routines use Delay_Cyc routine. If requested baud rate is too low then calculated parameter for calling Delay_Cyc exceeds Delay_Cyc argument range.
If requested baud rate is too high then rounding error of Delay_Cyc argument corrupts Software UART timings.
Вариант №1 загрузка прошивки с помощью другого программатора.
Тут мы подключаем прошиваемый программатор к рабочему, +5V к +5V, GND к GND, SWCLK и SWDIO к отверстия как на рисунке ниже.
Если прошиваемый программатор отличается то выводы SWCLK и SWDIO для прошивки можно найти с помощью прозвонки мультиметром.
Перед тем как начать качаем архив с нужным софтом STM32 ST-LINK Utility v4.3.0 setup.exe и Protected-2-1-Bootloader.bin, разархивируем, устанавливаем программу для прошивки STM32 ST-LINK Utility v4.3.0 setup.exe и запускаем её. Подключаем наш прошиваемый программатор к рабочему и далее по инструкции ниже, если контроллер не заблокирован от чтения то начинаем с пятого пункта, файл для прошивки Protected-2-1-Bootloader.bin который тоже находится в архиве.
Если не понятно на картинках, делаем так:
1) жмём Connect;
2) если выскакивает табличка память заблокирована, выполняем следующие пункты инструкции если нет то переходим к пункту 6;
3) жмём Target->Option Bytes. ;
4) в Read Out Protection выбираем Disabled и жмём внизу на Apply, память контроллера сбросится и снимется защита;
5) жмём Connect;
6) жмём File->Open file. ;
7) находим файл Protected-2-1-Bootloader.bin;
8) жмём Target->Program & Verify. ;
9) в открывшемся окне где указан прошиваемый файл жмём Start;
10) контроллер должен начать прошиваться;
Надеюсь всё получилось теперь осталось настроить прошивку, переходим к > и там заканчиваем весь процесс.
Подключение stm32 к компьютеру по uart
Микроконтроллеры GIGADEVICE. Инструкция по освоению
Статья была опубликована в журнале Компоненты и технологии №7 2017 г.
авторы: Хафизов Даян, Смирнов Григорий, Александр Сыров, alexandr.syrov@eltech.spb.ru
На сегодняшний день рынок 32-битных микроконтроллеров на основе ядра Cortex-M3 довольно широк. Популярностью у разработчиков цифровых устройств на основе микроконтроллеров пользуется продукция таких производителей, как Microchip, ST Microelectronics, NXP и т.д. Однако, недавно появился еще один производитель, готовый составить им конкуренцию – GigaDevice со своей линейкой микроконтроллеров GD32. В данной статье будет рассказано о микроконтроллерах GD32, приведено сравнение с ближайшим конкурентом, а также представлены примеры работы с одним из представителей семейства GD32 в популярной IDE Keil MDK-ARM.
Рисунок 1. Логотип компании GigaDevice
Компания GigaDevice (рис.1) была создана в 2005 году в Пекине и вышла на китайский рынок с микросхемами памяти. В 2008 году GigaDevice начали выпуск микросхем памяти SPI NOR FLASH с напряжением питания 3,3 В по технологии 180 нм. Примечателен тот факт, что это первая полностью самостоятельная разработка в Китае. За последующие 5 лет GigaDevice укрепили свои позиции на рынке микросхем памяти и значительно улучшили технологии производства, быстро осваивая более высокие технологические нормы. Сейчас компания производит SPI NOR FLASH с напряжением питания 1,8 В по технологии 65 нм и занимает третье место в мире по объемам продаж в сегменте микросхем энергонезависимой памяти с объемом производства более 1 млрд микросхем в год. Компания GigaDevice в высокой степени сконцентрирована на инженерной работе, так в компании больше половины состава сотрудников – инженеры. Кроме этого, у компании GigaDevice более 100 патентов и около 500 заявок на патенты.
В 2013 году компания GigaDevice приобрела лицензию на ядро ARM Cortex-M3 и объявила о начале производства собственных 32-битных микроконтроллеров GD32. Внешнее сходство и сходство в наименованиях с микроконтроллерами от ST Microelectronics подталкивает к мысли о полном «копировании», но это не так. Несмотря на идентичность в расположении контактов и схожесть характеристик, отличия между GD32 и STM32 есть:
- рабочая частота до 108 МГц для семейства GD32F1 (у STM32F1 до 72 МГц),
- объем FLASH памяти до 3 Мб (у STM32F2 не более 1 Мб),
- объем оперативной памяти до 256 Кб (у STM32 не более 128 Кб),
- и проч.
По сравнению с «одноклассником» STM32F1, микроконтроллеров GD32F1 обладают лучшим набором характеристик. В случае, когда не хватает flash-а для программы или хранения данных, или же не хватает быстродействия, оптимально использовать GD32F1. Также большим плюсом GD32 является более низкая, нежели у конкурентов, цена.
Ну что же, плюсы и минусы есть у всех. Проверим работоспособность микроконтроллеров GD32 на реальном железе. Возьмем отладочную плату GD32103E-EVAL (рис. 2) и рассмотрим несколько простых примеров.
Рисунок 2. Внешний вид отладочной платы GD32103E-EVAL
Начало начал. Помигаем светодиодами.
Несмотря на то, что микроконтроллеры GD32 и STM32 не являются абсолютно идентичными устройствами, они являются совместимыми как по выводам, так и по карте регистров в рамках одного семейства. Следовательно, микроконтроллер GD32F103ZET6 будет совместим по выводам с STM32F103ZET6. Таким образом, мы можем воспользоваться популярным среди разработчиков генератором исходного кода STM32CubeMX для создания демонстрационного проекта (рис. 3). В данном случае, ключевым отличием GD32F103ZET6 от «собрата» STM32F103ZET6 являются максимальная частоте работы ядра: 108 МГц у GD, против 72 МГц у STM. Также отметим, что микроконтроллеры серии GD32F103 могут иметь до 3 Мб встроенной Flash памяти против 1 Мб у STM32. Это может стать решающим преимуществом GD32F103 перед STM32.
Рисунок 4. Настройки RCC для GD32F10x
Далее генерируем исходный код для Keil. После установки AddON-ов от GigaDevice в закладке Device появилась возможность выбрать MCU GD32 (рис. 5). В нашем случае выбираем GD32F103ZE.
Рисунок 5. Конфигурирование Keil для работы с GD32
В файле main.c добавляем в цикл:
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_1);
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_0);
HAL_Delay(1000);
Затем собираем проект и приступаем к «прошивке» контроллера. На отладочной плате GD32103E-EVAL уже присутствует программатор GD-Link (так же, как и почти на всех отладочных наборах у STM). Так что после установки утилиты от GigaDevice, можем прошивать микроконтроллер и заниматься отладкой программы из Keil. В закладке Debug выбираем CMSIS-DAP Debugger (рис. 6) и в настройках указываем определившийся адаптер (рис. 7). Жмем «OK», прошиваем контроллер, перезапускаем контроллер, наблюдаем за двумя мигающими светодиодами ☺
Рисунок 7. Окно выбора режима программатора
Итак, пока получается, что мало того, что для работы с GD32 можно использовать программное обеспечение от STM, так еще и библиотека HAL корректно работает.
Работа с UART, АЦП и таймерами.
Светодиодами помигали, теперь сделаем что-то посерьезнее. Будем снимать данные с АЦП и передавать их в UART. И делать это будем через прерывание от таймера.
Сделаем новый проект в STM32CubeMX. UART заведем на контакты PA9 (TX) и PA10 (RX), на PC3 заведем вход АЦП (рис. 8).
Рисунок 8. Конфигурирование входов-выходов для работы с UART и АЦП
Настроим UART как показано на рис. 9. Для этого перейдем на вкладку Configuration и выберем USART1.
Рисунок 9. Настройки UART
Также настроим ADC. Большая часть настроек остается по умолчанию.
Рисунок 10. Настройка АЦП
АЦП на входе PC3 подключен к потенциометру на 10 кОм (рис. 11). В новом проекте будем снимать значение напряжения с АЦП. Опрос АЦП будем производить в прерывании от таймера TIM6.
Рисунок 11. Подключение потенциометра к контакту РС3
Перейдем на вкладку Clock Configuration. В данном случае воспользуемся настройкой тактирования ядра и периферии, установленными по умолчанию: ядро тактируется частотой 48 МГц, тактирование периферии конфигуратор рассчитает автоматически (рис. 12).
Рисунок 13. Конфигурирование таймера TIM6
Таймер TIM6 сконфигурируем так, чтобы прерывание от него срабатывало 1 раз в секунду. Поскольку периферия тактируется с частотой 48 МГц, то для получения периода в 1 секунду значение предделителя приравниваем к 47999, а регистр счетчика – к 999 (рис. 13).
Сгенерируем проект и откроем его в Keil. Все обработчики прерываний находятся в файле stm32f1xx_it.c. Находим обработчик прерывания от таймера TIM6 и вставляем туда функцию отправки данных в UART, как на рис. 14.
HAL_UART_Transmit – функция HAL для отправки данных в UART, сгенерированная STM32CubeMX. Эта и другие функции HAL для UART находятся в файле stm32f1xx_hal_uart.c
transmitBuffer – в данном случае массив, который мы объявили глобально и в который просто записали строчку, которую хотим передавать в UART.
STM32F4EvaluationBoard, STM32F7EvaluationBoard — подключаем UART и ADC.
В предыдущей части мы знакомились с основами работы в связке System Workbench for STM32 + STM32CubeMX. Теперь познакомимся с работой UART и ADC.
Для работы нам потребуется проект, созданный по прошлой статье. Если у вас его нет, то по-быстрому изучите первую часть, чтобы понять основы.
Также нам потребуется терминальная программа для общения с виртуальным COM портом. Вы можете использовать любой терминал, который вам по вкусу. Если у вас нет такой программы, то я рекомендую вам Terminal_by_zloiMOZG, на основе которого и будут все примеры в статье. Терминал не требует установки.
Для работы с UART нам потребуется подключить джмаперы PA10 и PA9 в положения USART1_RX и USART1_TX соответственно. Таким образом, мы подключили RX и TX выходы к преобразователю USB-UART. Так как на нашей отладочной плате стоит USB-HUB, то и отладки, и работа с UART будут происходить через 1 подключённый кабель.
После того, как вы переключили джамперы, откройте ваш проект в System Workbench for STM32, составленный по предыдущему уроку, а также откройте проект STM32CubeMX.
Первым делом в STM32CubeMX нам следует включить USART1 в асинхронном режиме. Для этого в левом столбце найдите его и включите нужный режим, как на скриншоте:
Поле того, как вы включили USART1 у вас должны стать активными пины PA9 и PA10, как показано на скриншоте:
Теперь нам следует настроить режимы работы UARTа. Для этого в STM32CubeMX переходим в закладку Configuration и в колонке Connectivity нажать на иконку нашего USART1. После нажатия перед нами появится окно настройки. На данный момент нас интересуют только 2 закладки данного окошка, а именно Parameter Settings и NVIC Settings. В окне Parameter Settings выставляются основные параметры передачи данных, а в окне NVIC Settings включаются или выключаются прерывания данного UART. Выставите параметры как показано на скриншотах:
Как вы обратили внимание, мы активировали прерывания USART1.
После того, как все настройки сделаны, давайте попробуем сгенерировать наш проект, как его генерировать, было рассказано в предыдущей части. После этого переходим в System Workbench for STM32 с нашим кодом, жмём F5 для того, чтобы обновить проект и подтянуть изменения, сделанные STM32CubeMX . В функцию main должен добавиться вызов функции инициализации USART1.
Теперь можно попробовать написать самый простой тест для ком порта, а именно будем отправлять в цикле каждую секунду переменную, которую будем увеличивать на “1” после каждой отправки.
Делается это очень просто. В функции main и в бесконечном цикле while(1) пишем:
Что мы тут делаем?
- Объявляем одномерный массив test , с размером элемента 1 байт. Делается это потому, что функция HAL_UART_Transmit_IT может работать только с массивами.
- Собственно отправляем нашу переменную test по UART.
- Увеличиваем значение переменной на “1”.
- Ждём одну секунду. После этого процесс повторяется.
В окне терминала у вас должна быть примерно такая картина:
Если у вас получился такой же результат, значит первый тест мы сделали успешно. Теперь пробуем научиться правильно отправлять и принимать данные через UART в STM32.
*немного о “правильности”*
Очень часто в уроках по STM32 можно встретить рекомендации о том, как использовать прерывания. В 99% случаев вам будут говорить: “Для того, чтобы обработать прерывание, вам надо писать свой код в файле stm32f4xx_it.c. Найдите там требуемый хендлер и пишите код.” . Для нашего случая, когда мы используем USART1 вам было бы предложено использовать void USART1_IRQHandler(void).
Это НЕПРАВИЛЬНО!
Дело в том, что ARM архитектура предполагает всего 1 вектор прерывания на ВСЕ события, которые могут произойти для определённого интерфейса или модуля. То есть в данный хендлер мы попадём как в случае удачной отправки или приёма, так и в случае возникновения ошибки. Для того, чтобы узнать по какому поводу случилось прерывание, мы можем либо по хардкору прочитать нужный регистр и понять из него, что же случилось. Ну или можем воспользоваться написанными за нас колбеками по разным событиям, которые можно для нас воспринимать как вектор прерывания по определённому событию. По сути в библиотеке HAL уже реализован “разбор полётов”, по какому поводу произошло прерывание, и уже написаны отдельные колбеки для разных событий. Найти колбеки для UART можно в файле stm32f4xx_hal.c. Самый простой способ найти – это нажать Ctrl+F и в поиске ввести “__weak” примерно так:
Чтобы добавить работу с колбекам в ваш код, просто скопируйте функцию только без приписки __weak, и вставьте её в область “USER CODE” перед функцией main. Примерно так:
Как вы можете обратить внимание, в самих колбеках я уже вставил проверку на то, какой UART стал причиной попадания в колбек. Как можно догадаться, колбек у нас тоже “один на всех”. В случае, когда мы используем, к примеру, только один UART, нам это вообще никак не мешает. Но если используемых UARTов уже два, вот тут нам и пригодится знание о том, кто же у нас был причиной попадания в колбек. МЫ работаем с USART1 и соответственно, проверяя в колбеке if(huart == &huart1) мы можем точно убедится, что событие произошло именно по нужной нам причине.
С отправкой данных мы уже разобрались, теперь давайте попробуем понять, как данные принимаются.
Для приёма данных по UART у нас есть функция:
Работа этой функции немного не очевидна для новичков. Итак, в данной функции мы сообщаем, что когда-то нам должно прийти какое-то кол-во байт (конкретно в этом примере мы сообщаем, что в USART1 ( &huart1 ,) должны придти данные, их надо положить в переменную (или массив, данная функция поддерживает работу и с тем и с тем) receive_val . Ожидаемый объём данных равен одному байту ( (uint16_t)1 )). И теперь мы попадём в HAL_UART_RxCpltCallback только тогда, когда нам придёт именно запрошенное кол-во байт. Данная функция не блокирует работу контроллера. Мы как бы сообщили, что ждём посылку, и всё. МК начал её ждать, в это время занимаясь выполнением другого кода. Как только данные в нужном кол-ве придут, у нас сработает колбек, и мы уже сможем их обработать, забрав из переменной или массива receive_val . Но надо сразу понять, что если мы ожидаем ещё данные, нам следует опять вызвать функцию HAL_UART_Receive_IT для того, чтобы “запросить” ещё данных. То есть если мы постоянно ждём ещё данных, мы постоянно их запрашиваем, каждый раз после прихода очередной порции.
Мне кажется, вам уже не терпится попробовать принять данные. Но ведь просто так гонять чиселки не так интересно. Давайте напишем код, в котором контроллер должен будет отвечать на определённый байт (например байт должен быть равен числу 100), пришедший по UART байтом, в котором будет записано значение с одного из каналов АЦП.
А для этого изучим, как же АЦП работает.
2. ADC
Для настройки АЦП в первую очередь нам следует переключить джампер PA0 на припаянный к отладочной плате переменный резистор. В том, как это сделать, вам поможет шелкография на плате. Напомню, что речь идёт о отладочных платах STM32F4EvaluationBoard и STM32F7EvaluationBoard. Если у вас другая отладочная плата, то подключите переменный резистор к пину PA0 любым удобным вам способом в режиме делителя напряжения.
После того, как вы переключили джампер или подключили переменный резистор, откройте проект в STM32CubeMX, в котором мы работали с UART, и включите ADC1 поставив галочку около IN0. В результате у вас должно получится вот так:
Теперь переходим в закладку Configuration и в колонке Analog жмём на иконку ADC1.
В появившемся окошке делаем настройки как показано на скриншотах:
Как можно понять из скриншотов, мы настроили АЦП на выдачу результата в формате 1 байта, каждое преобразование должно вызывать программно. А так же мы включили прерывания.
После этого мы генерируем код, и возвращаемся к System Workbench for STM32. Обновляем проект, как это было описано выше, после чего у нас добавляется инициализация ADC1.
Также нам следует добавить в код колбек срабатывающий по окончанию преобразования АЦП, с обязательно проверкой на то, что сработал колбек именно по причине события, вызванного нашим АЦП.
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) if(huart == &huart1)
Итак, как же работает АЦП.
Для того, что бы начать преобразование, нам нужно вызвать функцию HAL_ADC_Start_IT(&hadc1); . Эта функция запустит преобразование АЦП, в нашем случае единичное, так как мы настроили это в STM32CubeMX. То есть после того, как будет произведено 1 преобразование, мы получим 1 результат, АЦП прекратит работу до тех пор, пока мы ещё раз не вызовем функцию HAL_ADC_Start_IT(&hadc1) ; . Есть в АЦП ещё режим постоянного преобразования, когда данные он берёт с канала с определённой, настроенной ранее, частотой. В этом случае, если мы сделаем старт, АЦП будет молотить до тех пор, пока мы ему не скажем ХВАТИТ при помощи функции HAL_ADC_Stop_IT(&hadc1); .
Так как функция у нас с припиской _IT, это значит что АЦП будет работать, вызывая прерывание после окончания каждого преобразования. Как вы могли заметить, при вызове функции мы не передаём массив, куда надо складывать полученные значения. Чтобы получить значение АЦП из регистра в переменную или элемент массива, нужно присвоить переданное значение функции HAL_ADC_GetValue(&hadc1); То есть сделать вот так:
Как видно, мы делаем это в колбеке, который вызывается по окончанию преобразования, так как именно тогда мы можем точно знать, что АЦП закончил преобразование, и мы можем забрать актуальное значение. На данном скриншоте показано, как в нулевой элемент массива n кладётся результат преобразования ADC1.
Как работает АЦП, мы немного разобрались. Давайте попробуем написать программу, которая будет отправлять нам результат 1го преобразования АЦП после того, как по UARTу придёт байт равный 100.
Первое, что нам понадобится – это ряд переменных:
А так же код прописанный в колбеках:
Ну и конечно код в функции main:
Немного о том, что тут происходит:
После запуска микроконтроллера и инициализации всего, что нужно, мы вызываем функцию HAL_UART_Receive_IT(&huart1,&receive_val,(uint16_t)1) ; , тем самым говоря, что ждём прихода одного байта по UART и хотим, чтобы пришедший байт был положен в переменную receive_val . После чего в бесконечном цикле мы постоянно смотрим, не стала ли равна единице переменная status_val, а равной “1” она станет, когда сработает HAL_UART_RxCpltCallback , который, как очевидно, сработает только после того, как нам придёт 1 байт по UART. В данном колбеке мы убедимся, что пришёл байт именно из нужного UART, если это так, то присвоим переменной status_val значение “1”, а так же скажем UARTу что мы ждём ещё 1 байт, при помощи уже известной функции HAL_UART_Receive_IT(&huart1,&receive_val,(uint16_t)1) ;.
Как только в бесконечном цикле мы увидим, что status_val стал равен единице, мы подними ножку PE0, на которой у нас висит синий светодиод, тем самым заставив его светится. После этого мы сравним пришедшее по UART значение, лежащие в переменной receive_val с нужным нам значением “100”, и если это так, то мы подождём 10мс. Это нужно для того, что бы светодиод успел нормально зажечься и как бы моргнул. После этого присвоим переменной ADC_flag значение “busy” и запустим преобразование вызвав функцию HAL_ADC_Start_IT(&hadc1); .
В цикле while(ADC_flag == busy); мы ждём пока переменная ADC_flag не перестанет быть равной busy . А перестанет она, когда мы попадём в HAL_ADC_ConvCpltCallback , где мы как изменим значение переменной, так и заберём результат преобразования АЦП.
После этого в ещё одном цикле while (UART_status_val == busy); мы проверим, не занят ли наш UART в данный момент какой-либо отправкой. Соответственно, ждать будем пока не перестанет быть занятым. Состояние переменной UART_status_val изменяется в HAL_UART_TxCpltCallback .
Ну и соответственно, убедившись, что всё готово для передачи данных, вызываем функцию отправки данным в UART. HAL_UART_Transmit_IT(&huart1,(uint8_t*)n,(uint16_t)1);
Конечно, не забыв поставить флаг занятости UART, обнулив переменную с перешедшими по UART данными и погасив светодиод:
UART_status_val = busy;
receive_val = 0;
HAL_GPIO_WritePin(LED_Blue_GPIO_Port,LED_Blue_Pin,GPIO_PIN_RESET);
Запустив нашу программу на отладочной плате и запустив терминальную программу, вы можете увидеть, как в ответ на отправленное число “100” нам приходит значение АЦП. То, что это число – именно значение АЦП можно убедиться, покрутив соответствующий переменный резистор. А также у вас будет кратковременно моргать синий светодиод.
В терминале это выглядит примерно так:
Тут я 4 раза отправил число 100, предварительно вращая переменный резистор.
Также вы можете отметить, что если вы отправите любое, отлично от “100” значение, то в ответ в терминал данные не придут, а синий светодиод загорится ярким светом, и будет гореть ровно до тех пор, пока вы опять не отправите значение “100”.
На этом всё. В следующий раз мы рассмотрим работу ЦАП. Очень желательно, чтобы для этого у вас был вольтметр или осциллограф.
Проект с кодом, написанным для этой статьи можно скачать тут. Распакованную папку следует подключать к «System Workbench for STM32» как «workspace».