LED controller: оживление макета

Уф! Это было нелегко! Почти две недели я убил на попытки оживить макет LED_controller’а. Большая часть этого времени была убита в кровопролитных боях с LWIP стеком и FreeRTOS. Собственно, даже не с ними, а с инструментом для генерации начального кода (инициализации) STM32CubeMX. Но обо всем по порядку.

Внешний вид макета в сборе

Первым делом надо было макет собрать. Отладочную плату надо было соединить с платой драйверов — подать туда 2 канала ШИМ. Кроме того, контроллер надо было как-то прошивать — куда всё это подключать?? Так как плата заказывалась в Китае — поставлялась она в комплектации «AS IS», что в вольном переводе с вражеской мовы означало «ебись с этим сам, как хочешь».  Требовалось раздобыть схему отладочной платы. Первым делом, я, конечно, обратился к продавцу. Но, не особо рассчитывая на его пролетарскую сознательность, полез гуглить сам. Плата носит гордое название ZQ_STM32F107_Mini{что-то ещё иероглифами}V3.0. К моему удивлению, схема нашлась очень быстро. Продавец, что характерно, оказался сознательным гражданином и предложил выслать мне схему на мыло (не забыв, попутно занести мой адрес в списочек для рассылок рекламы).

Подключив к плате отладчик и накидав простенькую программку с помощью теплого лампового StdPeriph_Driver я радостно загрузил в контроллер blink.elf 🙂 В волю насмотревшись на мигающие на плате светодиоды, я решил, что пора бы уже приступать к чему-то посерьезнее. Для генерации ШИМ надо было сконфигурировать таймер, а для этого неплохо бы знать, как и чем мы тактируемся.

Схема отладочной платы. Кварцевый резонатор

По схеме получалось, что контроллер тактируется от внешнего резонатора с частотой 25 МГц. И по факту на плате также стоял резонатор 25 МГц. Это еще один важный шаг при отладке/регулировке электронных изделий — довольно часто в моей практике бывало, что причина неработоспособности какого-либо узла связана с установкой элементов не того номинала (хотя есть куча документации, специально обученные люди, занимающиеся подбором компонентов и т.д.). Цифра 25 меня несколько смутила — в голове, почему-то осталось, что внешний кварц для STM32F1xx должен быть в пределах 3 — 16 МГц. Повторное внимательное чтение даташита показало, что всё нормально — внешний кварц для STM32F107 может быть 3 — 25 МГц.

Дальше надо было настроить систему тактирования контроллера. Для этого пришлось установить STM32CubeMX. Написана эта гнусность на Java, поэтому для запуска требует наличия установленного JRE. Ну и не зависит от операционки — у меня под линухом работает нормально. Собственно, CubeMX мне нужен был только ради красивой интерактивной схемы тактирования.

Схема тактирования контроллера STM32F107 из CubeMX

Настроив там main clock на частоту 50 МГц (почему 50 — об этом чуть ниже), я начал настраивать конфигурацию ручками, через StdPeriph_Driver, опираясь на эту схему. Это не заняло много времени и в результате получилось что-то похожее на это:

void MCUConfigRCC(void)
{
// Resets the RCC clock configuration to the default reset state.
RCC_DeInit();
// Configure PREDIV2 (look for STM32 clock diagram) division factor
RCC_PREDIV2Config(RCC_PREDIV2_Div5);
// Configure PLL2 (look for STM32 clock diagram) multiplication factor
RCC_PLL2Config(RCC_PLL2Mul_8);
// Configure PREDIV1 (look for STM32 clock diagram) clock source & division factor
RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div4);
// Configure PLL
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_5);
// Enable PLL2
RCC_PLL2Cmd(ENABLE);
// Enable PLL
RCC_PLLCmd(ENABLE);
// Configure AHB prescaler
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// Configure APB1 prescaler
RCC_PCLK1Config(RCC_HCLK_Div2);
// Configure APB2 prescaler
RCC_PCLK2Config(RCC_HCLK_Div1);
// Main Clock Out config
RCC_MCOConfig(RCC_MCO_SYSCLK);
// HSE oscillator ON
RCC_HSEConfig(RCC_HSE_ON);
while(RCC_WaitForHSEStartUp() != SUCCESS)
{
//wait until HSE starts up
}
// Switch System clock to PLL output
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
}

После того, как мы настроили тактирование и точно знаем какая там частота — настройка таймера пройдет легко и приятно. Единственная вещь, которая от меня по первости ускользнула — это то, что после настройки таймера его надо запустить и запустить генерацию выходных ШИМ сигналов.

// Конфигурация таймера
TIM_TimeBaseInitTypeDef TIM_BaseConfig;
// Конфигурация выхода таймера
TIM_OCInitTypeDef TIM_OCConfig;

// Запускаем таймер на тактовой частоте в 10 MHz
TIM_BaseConfig.TIM_Prescaler = 4;
// Период - 1000 тактов => 10 kHz
TIM_BaseConfig.TIM_Period = PWM_PERIOD;
TIM_BaseConfig.TIM_ClockDivision = 0;
// Отсчет от нуля до TIM_Period
TIM_BaseConfig.TIM_CounterMode = TIM_CounterMode_Up;
// Инициализируем таймер №1
TIM_TimeBaseInit(TIM4, &TIM_BaseConfig);
// Конфигурируем выход таймера, режим - PWM1
TIM_OCConfig.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCConfig.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCConfig.TIM_Pulse = 10;
TIM_OCConfig.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM4, &TIM_OCConfig);
// Конфигурируем второй выход таймера
TIM_OCConfig.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCConfig.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCConfig.TIM_Pulse = 10;
TIM_OCConfig.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM4, &TIM_OCConfig);
// Включаем таймер
TIM_Cmd(TIM4, ENABLE);
TIM_CtrlPWMOutputs(TIM4, ENABLE);

Собрав и запустив это художество, я увидел на осциллографе заветные ШИМ сигналы. Оставался еще Ethernet и вроде бы его можно было и не проверять на макете. Но, памятуя о законе электротехнической кармы, я решил, что лучше проверить и уточнить все нюансы сейчас, т.к. перезаказ платы — это дорого и больно :). Прежде всего я воткнул патч-корд в розетку на отладочной плате и… не увидел линка. Это меня несколько насторожило и я обратился к схеме.

Схема отладочной платы. Ethernet PHY

В качестве физики (трансивера физического уровня Ethernet) на плате стояла микросхема DP83848 от Texas Instruments. Первое, что бросилось в глаза — это отсутствие отдельного резонатора или генератора для тактирования физики. Ага, значит надо подать его с контроллера. Физика здесь подключена по интерфейсу RMII (reduced MII), но подключенная к X1 нога контроллера PA1(ETH_REF_CLK) тоже является для него входом! Я уже начал было грешить на нерадивых китайских разработчиков, но тут заметил, что нога PA1 контроллера соединена с PA8 контроллера же. Поначалу меня это ни на какие мысли не натолкнуло (я подумал, что это связано с возможным Remap’ом MII/RMII интерфейса), но окончательную ясность внес апноут на DP83848 AN-1405 DP83848 Reduced Media Independent Interface (RMII) Mode.

Схема подключения DP83848 в режиме RMII

Сразу стало понятно, что: 1) физике нужна частота 50 МГц (отсюда и настройки тактирования контроллера); 2) тактировать физику и MAC контроллера мы будем, выводя на ногу PA8 основной тактовый сигнал контроллера (MCO — Main Clock Output). Осталось только реализовать все это в коде.

Но тут меня ждал неприятный сюрприз — в теплой и ламповой библиотеке StdPeriph_Driver не оказалось ничего, что могло бы мне помочь сконфигурировать внутренний Ethernet MAC контроллера. И в прилагаемых к библиотеке примерах также не нашлось примера с Ethernet’ом. Это меня расстроило, т.к. времени ковыряться в регистрах не было, хотелось уже завести Etthernet и перейти от макета к разработке «боевого» изделия. Пришлось воспользоваться инструментом генерации кода CubeMX, который вместо StdPeriph_Driver использует богомерский HAL_Driver. Такое ощущение, что этот самый HAL писали 800 индусов по строчке каждый. Куча уровней вложенности, код получается непонятный. Если что-то пошло не так — докопаться до истины очень сложно. Но пришлось пользоваться тем, что есть.

И тут сразу всё пошло не так. Началось с того, что выводя MCO (50 МГц на секундочку!) на PA8 в режиме альтернативной функции (GPIO_MODE_AF_PP) CubeMX по умолчанию настраивает ногу как Low speed!

/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

Пришлось править вручную. Если оставить такую настройку — физика не заводится! Если исправить на GPIO_SPEED_FREQ_HIGH — сразу появляется линк. Итак, линк появился — самое время добавить туда LWIP стек. Добавляю стек, генерирую код — и… из main() куда-то пропадает вызов функции инициализации Ethernet’а MX_ETH_init()! Оказалось, что всё не так страшно — функция и вызов никуда не потерялись. Просто теперь MAC инициализируется в недрах MX_LWIP_Init().

MX_LWIP_Init—>ethernetif_init—>low_level_init

hal_eth_init_status = HAL_ETH_Init(&heth);

Видимо, я чего то не понимаю в этой жизни. Но зачем, Карл??! Ладно, пусть это останется на совести создателей CubeMX, если, конечно, она у них есть. В этом месте я вспомнил, что неплохо бы прикрутить к этому FreeRTOS, чтобы было удобнее работать, так как нам нужно 2 потока — один для Ethernet’а, другой — для опроса кнопок и других органов управления. А поскольку, читаемость кода у нас давно уже нарушена — почему бы и не добавить 🙂

При попытке собрать всё вместе (LWIP & FreeRTOS) компилятиор выдает ошибку:

redefinition of ‘struct timeval’ sockets.h /maket_rtos/Middlewares/Third_Party/LwIP/src/include/lwip line 442

Это вызвано тем, что при добавлении FreeRTOS подключается <sys/time.h> в котором эта структура уже определена и в sockets.h происходит её переопределение. Ответ на извечный вопрос Чернышевского («Что делать?») находится там же в sockets.h:

/** LWIP_TIMEVAL_PRIVATE: if you want to use the struct timeval provided
* by your system, set this to 0 and include <sys/time.h> in cc.h */

#ifndef LWIP_TIMEVAL_PRIVATE
#define LWIP_TIMEVAL_PRIVATE 1
#endif

#if LWIP_TIMEVAL_PRIVATE
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
#endif /* LWIP_TIMEVAL_PRIVATE */

Ставим #define LWIP_TIMEVAL_PRIVATE 0 и радуемся жизни. Теперь я дописал в основную задачу опрос кнопок и логику включения/выключения каналов, изменения яркости, а в другую — периодическую отсылку широковешательного сообщения с состоянием выходов (значения скважности ШИМ каналов в попугаях). Устроившись поудобнее, я приготовился лицезреть результаты своего труда.

Но не тут то было. ШИМ и кнопки работали нормально, скважность менялась как положено. А вот задача с Ethernet’ом постоянно падала. Следующие пару дней я игрался с размером стэка задач, с их приоритетами, с RTOS’ом и его памятью. Иногда пакеты отправлялись, иногда задача падала сразу. И вот после очередного изменения чего-то там я получил стабильный вылет в HardFault. Это был несомненный успех! Поразмыслив немного над HardFault’ом я сделал неожиданный(!) вывод, что задачи запускаются одновременно, а вызов инициализации LWIP (в недрах которого происходит инициализация Ethernet’а) CubeMX ставит внутрь одной из задач (DefaultTask). И поигравшись с приоритетами, я добился того, что вызов api стека происходит из второй задачи еще до инициализации Ethernet MAC. Ну, тут, как говорится, сам себе злобный буратино! 🙂

Перенеся вызов MX_LWIP_Init() в сетевую задачу я получил стабильную работу. Наконец-то стали приходить пакеты с попугаями :).

Широковещательные пакеты от макета

Ну, и наконец, как это выглядит в реальности:

В результате макетирования мы выяснили, что:

  • драйвер, управляемый ШИМ дает необходимую регулировку яркости;
  • представленное в отладочной плате схемотехническое решение для Ethernet успешно работает и можно его смело пихать в «боевую» схему;
  • концепция устройства в целом — рабочая, можно реализовывать.

Следующим этапом будет схемотехника «боевой» платы.

About the Author: admin

8 комментариев

  1. Подскажите, пожалуйста. Есть аналогичная отладочная плата, никак не могу разобраться с ethernet на ней. Ваша статья очень помогла, про тонкость с GPIO_SPEED_FREQ_HIGH сам бы вряд ли догадался, но дальше у меня затык. Делаю всё вроде бы правильно, линк поднимается, но плата не получает адрес по DHCP, а если задать статическую адресацию, то не пингуется. Уже неделю пытаюсь решить эту проблему, но зашёл в тупик. Подскажите, нет ли у вас возможности выложить проект для данной отладки, чтобы работал ethernet? Спасибо.

  2. Этот проект для отладки у меня не сохранился. Попробую что-нибудь накидать быстренько, чтобы Ethernet только работал.

  3. Вот тут лежит — https://github.com/engineering-bay/ZQ_STM32F107_Mini_EthernetUdpTest. Целиком проект для AtollicTrueSTUDIO. Там FreeRTOS и простой UDP клиент написан, который бродкастом шлёт пакеты каждые 2 секунды. Там же на всякий случай приложен файл .ioc проекта Stm32CubeMX. И при использовании RTOS обратите внимание на выделение памяти для неё — по дефолту выделяется очень мало (около 4 кБ).

  4. Спасибо большое! Импортировал ваш проект в STM32CubeIDE, компиляция прошла без проблем, загрузил в плату, воткнул в роутер, плата получила адрес по DHCP, потом wiresharkом посмотрел — да, летят от неё пакеты. Что-то где-то я напутал, значит. Завтра на свежую голову буду сидеть и сравнивать, отпишусь тут потом, вдруг ещё кому поможет.
    По поводу памяти для FreeRTOS, это TOTAL_HEAP_SIZE?

  5. Да, TOTAL_HEAP_SIZE это размер кучи, выделяемой для RTOS.

  6. Очень полезная статья. Есть острая необходимость сделать ONVIF PTZ поворотку. Купил данный девайс. Может поможет. Не сохранилось ли его описания, Или кто поможет.

Добавить комментарий