Как то совпало, что с момента начала моего более серьезного ковыряния STM32, ST electronics решили отказаться от библиотеки SPL, которую я использовал в предыдущих статьях. Думаю инфа не первой свежести. Тем не менее, ковырять новое детище решил не сразу, поэтому в последнее время просто наблюдал за развитием ситуации.
Для тех кто не в курсе. На текущий момент для инициализации и работы периферии имеются следующие варианты:
1. Использовать регистры (CMSIS)
2. Продолжать использовать SPL
3. Использовать HAL
4. Изобретать свой велосипед
1. В принципе идея такая же, как используется в AVR, т.е. есть портD нужно включить 12 ножку, то делаем так:
GPIOD->BSRRL = GPIO_BSRR_BS_12; |
Но это еще более менее понятная запись, обычно начинающим выносят мозг такие записи:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; |
Повторюсь, подобный же подход используется в AVR, там с этим нет проблем, ибо настроек не так много как у STM. В целом подобный подход подразумевает чтение даташита и все бы хорошо, но проблема в том, что его практически никто не читает, кроме того нет переносимости кода между разными сериями.
Тем не менее, решил испробовать. Честно открыл даташит, нашел страницу с описанием инициализации и написал код. Не заработало, причина не важна, сама суть в том, что пришлось гуглить и ковыряться внутренностях той же SPL, чтобы понять проблему. В итоге оказалось, что информация разбросана по даташиту в нескольких местах, поэтому требуются танцы с бубном.
С точки зрения общего развития это хорошо, много нового узнал, но конкретно в тот момент нужно было по быстрому родить простой кусок кода и вместо 5 минут потратил около часа. Конечно в следующий раз, ковыряться уже не нужно будет, но могут быть и более сложные вещи на изучение которых можно потратить намного больше времени. В общем данное занятие на любителя.
2. Хоть SPL и не рекомендуется к употреблению, но с ее использованием написано уже много примеров и она есть для основных для меня 103 и 407 серии. Ее подход мне вполне понятен, на ней проще учиться. Для рядового радиолюбителя, который планирует использовать ограниченный потенциал микроконтроллера в принципе большего и не надо. Но опять же столкнулся с тем, что перенос кода между 103 и 407 требует внимательной правки ручками и долбления в даташит.
3. Новое детище ST, которое пришло на смену SPL. Где то видел мнение, что HAL это обертка над SPL. Ради интереса поковырялся, некоторые куски действительно похожи, но в большинстве своем совершенно разные. Хорош HAL тем, что он используется с Cube Mx — генератором кода для STM, некий аналог CAVR для AVR, т.е. проект можно сгенерить парой кликов мышки. Тогда почему просто не использовать HAL? Проще всего ответить на этот вопрос так: сгенерируй проект и посмотри на код, если нет рвотных позывов, то милости просим. Лично по мне, наворочено много лишнего, там где это не нужно, хотя это скорее дело привычки.
4. Ну и тем кому не нравятся библиотеки от ST изобретают свой велосипед. Вероятно, если бы я работал только с одной серией и рожал много прошивок, то выбрал бы именно этот подход.
В итоге, можно считать, что ситуация закончилась победой HAL, ибо Keil и Cocox полностью избавились от SPL. Понемногу появляются статьи с использованием HAL. Но в общем то кто как хочет, тот так и .. программирует. Честно сказать, то что генерирует Cube Mx это нечто монстроидальное, тяжело тупить в такой код.
Тем не менее, решил потихоньку вливаться в мейнстрим, исключительно ради спортивного интереса. Но чтобы не начинать снова со 100500 мигания светодиодом, затарился парочкой простых микросхем. Первой из них и самым простым будет цифровой резистор AD8400. Почему именно он? Да хз, что было то и взял.
Вкратце что это такое? Похоже очень на простой резистор, у которого есть два крайних вывода A1 и B2 между которыми постоянное сопротивление, и щетка W1 положение которой мы можем изменять.
Управляется он по SPI протоколу. Поэтому я подключил его так, чтобы с W1 можно было снимать напряжение:
Тот резистор, что есть у меня одноканальный, т.е. в одном корпусе один резистор, но есть такие же с двумя и четырьмя резисторами в одном корпусе. Бывают на 1, 10, 50, 100кОм. Хоть и было заявлено номинальное сопротивление 10кОм, на практике оно оказалось 8кОм, прозвонить его можно только когда подано питание. После включения на щетке будет рандомное значение.
Управление хоть и SPI, но по факту железный spi задействовать не удастся, ибо вначале идут 2 бита адреса, а затем 8 бит данных. В итоге за один раз нужно передавать 10бит. Весь диапазон резистора разбит на 256 значений, для 10кОм шаг будет в ~40 Ом.
Алгоритм работы будет следующий:
1. Прижимаем ножку CS к земле
2. С каждым тактом CLK выдаем 2 нуля на ножке MOSI
3. C каждым тактом CLK выдаем 8 бит данных на ножке MOSI
4. Поднимаем ножку CS
Для управления использовал программный spi. Сгенерил проект в Cube — тупо настроил PB13-PB15 как выходы. Лень было заморачиваться, поэтому сразу после включения, однократно посылаем байт на резистор. Для желающих можно, включить фантазию, например регулировать уровень звука с помощью энкодера или кнопки. Привожу два варианта кода, для SPL и HAL исключительно для сравнения, в последующих статья планирую юзать только HAL. Исходники прикреплять не буду, ибо 35мб для пустого кода больно жирновато. Тестировалось на STM32F4Discovery.
Для SPL
#include <stm32f4xx.h> #include <stm32f4xx_gpio.h> #include <stm32f4xx_rcc.h> //1.B1 - GND //2.GND //3.CS - PB14 //4.SDI(MOSI) - PB15 //5.CLK - PB13 //6.VDD - //7.W1 - test point //8.A1 - VCC // режим SPI / SPI MODE = 0 // назначение выводов порта #define SPI_SCK GPIO_Pin_13 // выход - SCLK #define SPI_SDI GPIO_Pin_15 // выход - MOSI #define SPI_SS GPIO_Pin_14 // выход - Chip Select #define SPI_PORT GPIOB // порт spi void _spi_start(void) { GPIO_ResetBits(SPI_PORT,SPI_SCK); // SPI_MODE = 0 GPIO_ResetBits(SPI_PORT,SPI_SS); // Chip Select - Enable } void _spi_stop(void) { GPIO_SetBits(SPI_PORT,SPI_SS); // Chip Select - Disable GPIO_ResetBits(SPI_PORT,SPI_SCK); // SPI_MODE = 0 } void _spi_init(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); GPIO_InitTypeDef s_SDI; s_SDI.GPIO_Mode = GPIO_Mode_OUT; s_SDI.GPIO_OType = GPIO_OType_PP; s_SDI.GPIO_Pin = SPI_SDI; s_SDI.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(SPI_PORT, &s_SDI); GPIO_InitTypeDef s_SCK; s_SCK.GPIO_Mode = GPIO_Mode_OUT; s_SCK.GPIO_OType = GPIO_OType_PP; s_SCK.GPIO_Pin = SPI_SCK; s_SCK.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(SPI_PORT, &s_SCK); GPIO_InitTypeDef s_SS; s_SS.GPIO_Mode = GPIO_Mode_OUT; s_SS.GPIO_OType = GPIO_OType_PP; s_SS.GPIO_Pin = SPI_SS; s_SS.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(SPI_PORT, &s_SS); } void _spi_sendbyte(unsigned char a) { unsigned char i; // отправить адрес 00 for(i=0; i<2; i++) { GPIO_ResetBits(SPI_PORT,SPI_SDI); GPIO_SetBits(SPI_PORT,SPI_SCK); GPIO_ResetBits(SPI_PORT,SPI_SCK); } for(i=0; i<8; i++) { if (a & 0x80) { GPIO_SetBits(SPI_PORT,SPI_SDI); } else { GPIO_ResetBits(SPI_PORT,SPI_SDI); } GPIO_SetBits(SPI_PORT,SPI_SCK); a <<= 1; GPIO_ResetBits(SPI_PORT,SPI_SCK); } } int main(void) { _spi_init(); _spi_start(); _spi_sendbyte(127); _spi_stop(); while (1); } |
С использованием HAL
#include "stm32f4xx_hal.h" void SystemClock_Config(void); static void MX_GPIO_Init(void); #define SPI_SCK GPIO_PIN_13 //SCLK #define SPI_SDI GPIO_PIN_15 //MOSI #define SPI_SS GPIO_PIN_14 //Chip Select #define SPI_PORT GPIOB //spi void _spi_start(void) { HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_RESET); HAL_GPIO_WritePin(SPI_PORT,SPI_SS,GPIO_PIN_RESET); } void _spi_stop(void) { HAL_GPIO_WritePin(SPI_PORT,SPI_SS,GPIO_PIN_SET); HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_RESET); } void _spi_sendbyte(unsigned char d) { unsigned char i; for(i=0; i<2; i++) { HAL_GPIO_WritePin(SPI_PORT,SPI_SDI,GPIO_PIN_RESET); HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_SET); HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_RESET); } for(i=0; i<8; i++) { if (d & 0x80) { HAL_GPIO_WritePin(SPI_PORT,SPI_SDI,GPIO_PIN_SET); } else { HAL_GPIO_WritePin(SPI_PORT,SPI_SDI,GPIO_PIN_RESET); } HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_SET); d <<= 1; HAL_GPIO_WritePin(SPI_PORT,SPI_SCK,GPIO_PIN_RESET); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); _spi_start(); _spi_sendbyte(64); _spi_stop(); while (1); } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; __PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; HAL_RCC_OscConfig(&RCC_OscInitStruct); RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1 |RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0); } void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* GPIO Ports Clock Enable */ __GPIOB_CLK_ENABLE(); /*Configure GPIO pins : PB12 PB13 PB14 PB15 */ GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } |
Питание 2.9В, отправленный байт 64, если пересчитать (2.9*64)/256 = 0,725В весьма похоже на то, что показал прибор на выходе.
В общем таки, если присмотреться то большой разницы нет, поэтому буду потихоньку переползать. Есть мнение что HAL содержит кучу багов, которые регулярно исправляются, поэтому рекомендую почаще обновляться.
Ого на що натрапив. Це ж треба 🙂 Дякую велике. Я вчився по вашим прикладам вісім років тому 😀