STM32 + linux

    Для разработки системы управления одной железякой после длительных поисков мною был выбран ARM-микроконтроллер семейства STM32 — STM32F103 (в «стоножечном» исполнении). А в качестве макетки для разработки и отладки — STM32P103 (там ножек хоть и меньше, но ядро то же самое). «Истории успеха» я понемногу выкладывал в своей ЖЖшке, но вот решил собрать все воедино и рассказать о том, каково же оно — программировать микроконтроллеры в линуксе. Сам проект лежит на sourceforge.



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

    Итак, помимо макетки (или же готового устройства — когда оно будет готово) потребуется JTAG-адаптер. В моем случае это — ST-LINK/V2. Одного железа, естественно, недостаточно: надо еще как-то код компилировать, а потом еще и заливать на контроллер. Для этого были установлены компилятор gcc для ARM (arm-none-eabi) и утилита для работы с ST-LINK (она так и называется — stlink).

    В качестве образца я взял этот проект. Отсюда я скачал простенькие демонстрационные проекты и попробовал скомпилировать простейший. А самым первым оказался стандартный Helloworld для МК: мигание светодиодом.

    Сразу скажу, на какие грабли я напоролся с самого начала: я забыл про objcopy, без которого получить работающий код будет сложновато. После компиляции проекта обязательно надо создать бинарник при помощи этой утилиты. И не стоит выкидывать из Makefile'ов непонятных (и вроде бы даже ненужных на первый взгляд) целей.

    В качестве удобного IDE я использую Geany. Так как на работе у меня два монитора, довольно удобно работать: на одном мониторе у меня открыт Geany с кодом, а на втором — терминал, где я запускаю make и com (терминал из tinyserial).

    Весь Makefile я рассматривать не буду, обращу лишь внимание на то, что в нем следует менять:
    Код
    BIN=testproject
    …
    STM32_LIBSRC+=stm32_lib/misc.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_adc.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_bkp.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_can.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_cec.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_crc.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_dac.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_dbgmcu.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_dma.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_exti.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_flash.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_fsmc.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_gpio.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_i2c.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_it.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_iwdg.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_pwr.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_rcc.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_rtc.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_sdio.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_spi.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_tim.c
    STM32_LIBSRC+=stm32_lib/stm32f10x_usart.c
    #~ STM32_LIBSRC+=stm32_lib/stm32f10x_wwdg.c
    …
    SRC=hw_config.c  main.c leds.c  interrupts.c  usb_desc.c usb_istr.c \
    	usb_prop.c  usb_pwr.c onewire.c
    …
    #~ OBJ+=stm32f10x_bkp.o
    #~ OBJ+=stm32f10x_can.o
    #~ OBJ+=stm32f10x_cec.o
    #~ OBJ+=stm32f10x_crc.o
    #~ OBJ+=stm32f10x_dac.o
    #~ OBJ+=stm32f10x_dbgmcu.o
    OBJ+=stm32f10x_dma.o
    OBJ+=stm32f10x_exti.o
    #~ OBJ+=stm32f10x_flash.o
    #~ OBJ+=stm32f10x_fsmc.o
    OBJ+=stm32f10x_gpio.o
    #~ OBJ+=stm32f10x_i2c.o
    #~ OBJ+=stm32f10x_it.o
    #~ OBJ+=stm32f10x_iwdg.o
    #~ OBJ+=stm32f10x_pwr.o
    OBJ+=stm32f10x_rcc.o
    #~ OBJ+=stm32f10x_rtc.o
    #~ OBJ+=stm32f10x_sdio.o
    #~ OBJ+=stm32f10x_spi.o
    #~ OBJ+=stm32f10x_tim.o
    OBJ+=stm32f10x_usart.o
    #~ OBJ+=stm32f10x_wwdg.o
    

    • BIN — имя получающегося после компиляции бинарника
    • STM32_LIBSRC и OBJ содержат подключаемые файлы библиотеки STDPeriphLib, неиспользуемые надо закомментировать
    • SRC сдоержит перечень пользовательских исходников


    После того, как код написан, запускаем make. Если все в порядке, в текущей директории появится файл $(BIN).bin, который и нужно записать во флеш-память МКшки. Запись выполняется при помощи make load: эта цель сборки просто вызывает st-flash для прошивки микроконтроллера.

    USB


    Итак, прежде всего необходимо наладить связь компьютера и МКшки. Учитывая то, что в современных компьютерах RS-232 отсутствует, отладочную связь организую посредством USB. Однако, в «боевых условиях» команды МКшка будет получать по RS-232 от другого контроллера, поэтому я решил сразу же смотреть в сторону организации эмулятора переходника USB<->RS-232. Этот подход удобен еще тем, что не надо заморачиваться с лишним кодом для взаимодействия с устройством по USB (хоть это и элементарно, но лень же!). Да и отлаживать просто: открываем устройство /dev/ttyACM0 как последовательный порт при помощи любого эмулятора последовательного терминала и «общаемся». Да, в качестве эмулятора терминала на первых порах (пока нет никакого ПО со стороны компьютера) я использовал tinyserial.

    Отсюда я скачал код эмулятора переходника USB<->RS-232. Так как проверить работоспособность второй стороны (RS-232) я сразу не мог (некуда подключить), неиспользуемый код работы с USART временно закомментировал.

    Для работы с USB используется библиотека от STMicroelectronics. Если не вникать в коды самой библиотеки, все довольно-таки просто: нам нужно переопределить дескрипторы для своей железяки (файлы usb_desc.[ch]), чтобы компьютер опознал ее как переходник USB<->RS-232, а также изменить обработчики прерываний на события USB (как минимум — обработать принятые данные, а для прозрачной работы в качестве переходника, надо будет еще добавить обработку прерываний USART для передачи полученных оттуда данных по USB).

    Для передачи сообщений используем что-то вроде кольцевого буфера, который постепенно будет наполняться, а по необходимости — передаваться по USB. Считывать данные будем в «обычный буфер». Так как пока что я использую только короткие команды, обработкой длинных посылок я не заморачивался. Если же они будут, нужно будет немного усложнить обработчик прерывания по приему данных с USB.

    Так как некоторые команды (например, чтение температуры с 1-wire датчиков) выполняются довольно-таки долго, обработчик пришедших по USB команд только модифицирует флаги для подобных операций, а уж основной цикл в main() эти флаги обрабатывает. Операции, выполняющиеся быстро (работа со светодиодом), вызываются непосредственно из этой функции. В отладочных целях я добавил «эхо» на команды в виде краткой ее расшифровки:
    Код
    void usb_handle_command(uint16_t cnt){
    	uint8_t command, *answer;
    	uint16_t i;
    	for(i = 0; i < cnt; i++){
    		command = USB_Rx_Buffer[i];
    		switch(command){
    			case CMD_LED_ON:
    				LED_On();
    				answer = (uint8_t*)"On";
    			break;
    			case CMD_LED_OFF:
    				LED_Off();
    				answer = (uint8_t*)"Off";
    			break;
    			case CMD_LED_BLINK:
    				LED_OnBlink();
    				answer = (uint8_t*)"Blk";
    			break;
    			case CMD_LED_DUTY_PLUS:
    				LED_DutyPlus();
    				answer = (uint8_t*)"Shn";
    			break;
    			case CMD_LED_DUTY_MINUS:
    				LED_DutyMinus();
    				answer = (uint8_t*)"Fad";
    			break;
    			case CMD_1W_GET_TEMP:
    				FLAGS |= FLAG_READ_T;
    				answer = (uint8_t*)"Read T";
    			break;
    			case CMD_1W_GET_DEV:
    				FLAGS |= FLAG_GETDEV;
    				answer = (uint8_t*)"find devices";
    			break;
    			case CMD_1W_PRNT_DEV:
    				FLAGS |= FLAG_PRINTDEV;
    				answer = (uint8_t*)"Print devices";
    			break;
    			case CMD_HALL_GET:
    				FLAGS |= FLAG_PRINTHALL;
    				answer = (uint8_t*)"Print Hall";
    			break;
    			case CMD_ADC_GET:
    				FLAGS |= FLAG_PRINTADC;
    				answer = (uint8_t*)"Print ADC val";
    			break;
    			default:
    				answer = (uint8_t*)"Unk";
    		}
    		newline();
    		prnt(answer);
    		newline();
    	}
    }
    


    Все, теперь при подключении макетки к компьютеру по USB (а она, вообще-то, у меня всегда подключена, т.к. питается через USB) появляется устройство /dev/ttyACM0, с которым можно работать, как с обычным последовательным портом. Например, открыть его при помощи последовательного терминала (как я уже выше сказал, на первых порах пользуюсь tinyserial).

    Светодиод, кнопка


    Наверное, традиционным является «помигать диодом» в начале изучения какой-нибудь новой железяки, поэтому и я сделаю так же. А заодно повешу на «user button» прерывание, которое будет менять режимы работы светодиода.

    Просто мигать не интересно: интересно менять яркость. Для этого достаточно простого «софтового» ШИМа. Настроим таймер SysTick на период в 10мкс. Заведем два счетчика: один для количества «тиков», в течение которых светодиод горит, а второй — для количества «тиков», в течение которых светодиод не горит. Для изменения яркости свечения светодиода я сделал простейшую восьмиуровневую схему изменения скважности ШИМа.
    Получилось вот что:
    Код
    uint8_t LED_GetState(){
    	return led_state;
    }
    void LED_Duty(uint8_t duty){
    	duty_cycle = duty;
    	if(led_state == LEDSTATE_BLINK)
    		LED_OnBlink();
    }
    void LED_DutyPlus(){
    	if(duty_cycle < 7) duty_cycle++;
    	if(led_state == LEDSTATE_BLINK)
    		LED_OnBlink();
    }
    void LED_DutyMinus(){
    	if(duty_cycle > 0) duty_cycle--;
    	if(led_state == LEDSTATE_BLINK)
    		LED_OnBlink();
    }
    uint8_t LED_GetBlinkState(uint16_t *blink_on, uint16_t *blink_off){
    	*blink_on = led_blink_on;
    	*blink_off = led_blink_off;
    	return led_state;
    }
    void LED_On(){
    	led_state = LEDSTATE_ON;
    	led_blink_on = 0;
    	led_blink_off = 0;
    	GPIO_ResetBits(GPIOC, GPIO_Pin_12);
    }
    void LED_Off(){
    	led_state = LEDSTATE_OFF;
    	GPIO_SetBits(GPIOC, GPIO_Pin_12);
    }
    void LED_OnBlink(){
    	led_blink_off = 1 << duty_cycle;
    	led_blink_on = 0xff - led_blink_off;
    	led_ticks_on = 0;
    	led_ticks_off = 0;
    
    	if(led_blink_off == 0){
    		LED_On();
    		return;
    	}
    	if(led_blink_on == 0)
    	{
    		LED_Off();
    		return;
    	}
    	led_state = LEDSTATE_BLINK;
    }
    void LED_SysTick_Handler(){
    	if(led_state != LEDSTATE_BLINK) return;
    	if(led_ticks_on == 0)
    		GPIO_SetBits(GPIOC, GPIO_Pin_12);
    	if(led_ticks_on <= led_blink_on)	{
    		led_ticks_on++;
    		return;
    	}
    	if (led_ticks_off == 0){
    		GPIO_ResetBits(GPIOC, GPIO_Pin_12);
    	}
    	if(led_ticks_off <= led_blink_off){
    		led_ticks_off++;
    		return;
    	}
    	led_ticks_on = 0;
    	led_ticks_off = 0;
    }
    


    На «пользовательскую кнопку» я повесил внешнее прерывание:
    Код
    	// Enable the BUTTON Clock
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	// Configure Button pin as input
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	// Connect Button EXTI Line to Button GPIO Pin
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    	// Configure Button EXTI line
    	EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    	EXTI_Init(&EXTI_InitStructure);
    	// Enable and set Button EXTI Interrupt to the lowest priority
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_Init(&NVIC_InitStructure);
    

    А его обработчик занимается тем, что переводит светодиод в режим непрерывного свечения, если тот «мигал», или наоборот — в режим «мигания», если тот светился:
    Код
    void EXTI0_IRQHandler(void){
    	if(EXTI_GetITStatus(EXTI_Line0) != RESET){
    		if(LED_GetState() != LEDSTATE_BLINK)
    			LED_OnBlink();
    		else
    			LED_On();
    		EXTI_ClearITPendingBit(EXTI_Line0);
    	}
    }
    


    1-wire


    Код для работы с 1-wire я стащил откуда-то с сайта easyelectronics.ru. Изменил я его совсем немного. Прежде всего, изменил функцию поиска устройств, висящих на шине (она в оригинале почему-то не работала, хотя логика вроде-бы вполне четкая и правильная была).

    В сворованном мною примере 1-wire работала через USART, а для чтения/записи использовался DMA. Мне эта идея очень понравилась, поэтому воспользовался именно этим способом (хотя можно было организовать и программный протокол 1-wire).

    Стандартная схема подключения 1-wire шины к последовательному порту подразумевает наличие диода Шоттки:

    Однако, у меня такого диода не было. Но я углядел, что помимо push-pull режима можно отвечающую за USART_TX ногу перевести в режим с открытым стоком — в этом случае короткого замыкания не будет. Для работы с 1-wire я использовал USART3 (пока я балуюсь, ног мне хватает — поэтому remap делать не надо). На схеме я увидел, что ноги USART3 (PB10 и PB11) уже подтянуты к земле через резисторы по 10кОм, так что мне даже резистор припаивать не пришлось: только подпаял на макетку небольшую платку с гнездами, чтобы удобно было подключать термодатчики.

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

    Для мониторинга температуры теплых (выше -50°C) частей устройства я решил воспользоваться простыми датчиками DS18S20 (заявленная точность измерения — не хуже 0.5°C). Подпаянную на макетку панельку я подключил к нужным выводам, чтобы можно было одновременно подключить к МКшке пару термометров.

    Вот, например, что я получаю при работе с термометрами:
    Код
    com /dev/ttyACM0
    C-a exit, C-x modem lines status
    [STATUS]: RTS CTS DTR
     // жму 'c' :
    find devices
    Found 2 devices
     // жму 'p' :
    Print devices
    device 0:
    0x10 0x7c 0xee 0x8f 0x02 0x08 0x00 0x1c
    device 1:
    0x10 0xad 0xbc 0x8f 0x02 0x08 0x00 0xf9
     // жму 't' :
    Read T
    Device 0: 0x3b 0x00 0x4b 0x46 0xff 0xff 0x08 0x10 0x39
    Device 1: 0x3a 0x00 0x4b 0x46 0xff 0xff 0x0c 0x10 0x41
    


    Для начала я промаркировал все термометры, чтобы знать, какой идентификатор у кого. А после я решил посмотреть, сильно ли различаются их показания. Температура — первые два байта ответа датчиков. В седьмом байте хранится «остаток» от преобразования температуры встроенным в термометр АЦП. По документации на датчики, этот остаток помогает уточнить значение температуры. Однако, выяснилось, что толку от него — как от козла молока.

    В процессе работы датчик сам нагревается, что сказывается на результатах измерения. Поэтому не стоит слишком часто их опрашивать. Кроме того, показания датчиков отличались друг от друга вплоть до полутора градусов! Это нужно иметь в виду: если планируется использовать несколько датчиков так, чтобы мониторить разницу температур между участками чего-то с точностью не хуже 0.5°C, предварительно надо все датчики откалибровать. И показания брать по калибровочным формулам, а не ответу датчиков.

    Реальная погрешность датчика иной раз превышает 0.5°C, поэтому все-таки лушче считать, что датчик имеет точность в 1°C.

    Датчик Холла


    Датчики Холла у меня аналоговые — SS495A. Спецификации на датчик можно найти в интернете. Скажу лишь, что в нормальном состоянии на его выходной ноге напряжение составляет около 2.5В (логическая единица STM32), в зависимости от полярности и величины внешнего магнитного поля он будет изменять свои показания в пределах 0..5В. Учитывая то, что напряжение на выходе может достигать пяти вольт, надо использовать не обычные, а «пятивольтовые» (обозначены как FR в спецификации) входы контроллера.

    Имеющиеся у меня магниты (специально для этого датчика) при помещении их рабочей поверхности в пределах 1мм от «морды» датчика (маркированная сторона) приводили к появлению на его выходе нулевого напряжения. Причем уровень логического нуля появляется в довольно-таки небольшой зоне по координатам в параллельной маркированной стороне датчика плоскости, т.е. точность позиционирования получается довольно приличной.

    Для опытов я распаял на макетке один датчик. Питание его подключил к 5В, а сигнальный выход вывел на порт PC10, который не сгорит, если на него подать 5В. Для того, чтобы не дергать постоянно порт, я повесил на него прерывание (по аналогии с кнопкой). Обработчик прерывания просто выставляет соответствующий флаг, а уж в основном цикле, если этот флаг выставлен (т.е. магнит либо появился, либо покинул «поле зрения» датчика) проверяем, что у нас на PC10. Если там ноль (есть МП), пишем в терминал «Magnet», иначе пишем «clear». Еще можно принудительно проверить, есть датчик или нет его, нажав «h» в терминале.

    АЦП


    Помимо «теплых зон» мне еще надо будет измерять температуру в холодных (вплоть до 75К сверху). Для этого будут использоваться платиновые термосопротивления, подключенные к аналоговому коммутатору ADG506A. Ну и, естественно, мне стало интересно, насколько плох «родной» АЦП МКшки: нельзя ли его использовать для измерения температуры?

    Примеров работы STM32 с АЦП полным-полно, я взял пример из STDPeriphLib. Будем запускать АЦП в режиме непрерывного преобразования, а результат заносить в память при помощи DMA. Время преобразования устанавливаю в самое большое (чтобы поточнее было), а сам вход АЦП пока что повешу на ногу PB0 (ADC8):
    Код
    	// 0. Configure ADC8 (PB0) as analog input (clocking GPIOB sat on in onewire.c)
    	RCC_ADCCLKConfig(RCC_PCLK2_Div4);
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOB, ENABLE);
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	// 1. DMA for converted value (DMA1 clocking sat on at onewire.c)
    	//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    	DMA_DeInit(DMA1_Channel1);
    	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
    	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_value;
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    	DMA_InitStructure.DMA_BufferSize = 1;
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    	DMA_Cmd(DMA1_Channel1, ENABLE);
    	// 2. ADC1 config
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    	ADC_InitStructure.ADC_NbrOfChannel = 1;
    	ADC_Init(ADC1, &ADC_InitStructure);
    	// Connect ADC to ADC8 (PB0),
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_239Cycles5);
    	// Enable ADC1 DMA
    	ADC_DMACmd(ADC1, ENABLE);
    	ADC_Cmd(ADC1, ENABLE);
    	// Calibration of ADC1
    	ADC_ResetCalibration(ADC1);
    	while(ADC_GetResetCalibrationStatus(ADC1));
    	ADC_StartCalibration(ADC1);
    	while(ADC_GetCalibrationStatus(ADC1));
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE); // turn conversion on
    


    Для работы с коммутатором нужно сконфигурировать пять бит управляющего порта. Чтобы не париться с преобразованием бит, я просто взял первые четыре бита порта C в качестве адреса, а пятый бит — в качестве ключа, включающего коммутатор:
    Код
    	GPIO_InitStructure.GPIO_Pin = 0x1f; // first 5 bits of PC0
    	// PC0..PC3 - analog channel address, PC4 - analog enable switch
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_Init(GPIOC, &GPIO_InitStructure);
    


    Прерываний здесь никаких не надо, а в файле interrupts.c надо дописать установку нового флага при поступлении команды (скажем, команды 'a') отображения напряжения на датчиках. В main() добавим обработку этого флага:
    Код
    inline void prntADC(){
    	uint32_t address; // addr = 0, EN = 1
    	uint8_t *_2b = (uint8_t *) &ADC_value;
    	for(address = 0x10; address < 0x20; address++){
    		// changhe channel address & turn on switch
    		GPIOC->BSRR = address;
    		Delay(2); // wait for AD conversion
    		prnt((uint8_t*)"Temperature ");
    		printInt(address&0x0f); prnt((uint8_t*)" = ");
    		printInt(_2b[1]);
    		printInt(_2b[0]);
    		newline();
    		// turn off switch & reset bits
    		GPIOC->BRR = (uint32_t)0x1f;
    		Delay(2);
    	}
    }
    

    При поступлении команды в цикле запускаем выставление нужного адреса на коммутаторе, ждем пару миллисекунд, чтобы АЦП отработало, а затем выводим полученное значение. Потом отключаем коммутатор и ждем (на всякий случай) еще пару миллисекунд.

    На отдельной макетке я собрал простой резистивный делитель напряжения, соединив все аналоговые входы коммутатора мелкоомными (200..900 Ом) резисторами. К S1 подключил «землю», а к S16 — +3.3В с макетки STM32. Запитал микросхему я старым БП от внешнего HDD (12В).

    В макетке STM32P103 эталонное напряжение для АЦП берется от общего питания, поэтому точность получилась низкой: значения плавают иной раз аж на 20 единиц!

    Вот, например, что получилось при двух опросах:
    Код
    // опрос 1
    Temperature 0x00  = 0x00 0x00
    Temperature 0x01  = 0x00 0x84
    Temperature 0x02  = 0x00 0xaf
    Temperature 0x03  = 0x01 0xdb
    Temperature 0x04  = 0x03 0x10
    Temperature 0x05  = 0x03 0xe4
    Temperature 0x06  = 0x05 0xca
    Temperature 0x07  = 0x06 0x9b
    Temperature 0x08  = 0x07 0x4e
    Temperature 0x09  = 0x08 0xd6
    Temperature 0x0a  = 0x0a 0x04
    Temperature 0x0b  = 0x0a 0xb4
    Temperature 0x0c  = 0x0b 0xfc
    Temperature 0x0d  = 0x0d 0xe0
    Temperature 0x0e  = 0x0e 0xb7
    Temperature 0x0f  = 0x0f 0xff
    
    
    // опрос 2
    Temperature 0x00  = 0x00 0x00
    Temperature 0x01  = 0x00 0x7f
    Temperature 0x02  = 0x00 0xaf
    Temperature 0x03  = 0x01 0xdf
    Temperature 0x04  = 0x03 0x0f
    Temperature 0x05  = 0x03 0xe4
    Temperature 0x06  = 0x05 0xcc
    Temperature 0x07  = 0x06 0x9d
    Temperature 0x08  = 0x07 0x5a
    Temperature 0x09  = 0x08 0xd6
    Temperature 0x0a  = 0x0a 0x01
    Temperature 0x0b  = 0x0a 0xb5
    Temperature 0x0c  = 0x0b 0xfc
    Temperature 0x0d  = 0x0e 0x09
    Temperature 0x0e  = 0x0e 0xb0
    Temperature 0x0f  = 0x0f 0xec
    


    В общем, надо будет либо попробовать добавить стабильный источник опорного напряжения (да и измеряемую цепь питать оттуда же), либо вообще использовать внешний АЦП. Учитывая низкое сопротивление датчиков, которые будут использоваться, все равно придется паять еще и усилитель.

    Шаговый двигатель


    С шаговиком я еще не закончил возиться, т.к. подозреваю, что при монтаже элементов на макетке у меня ничего «не взлетит». Надо паять. А пайкой займусь, скорее всего, только в следующем году (надо еще радиодеталей прикупить). Пока лишь вкратце расскажу, как планирую управлять шаговыми двигателями.

    Шаговики у меня будут — VSS42 на 1.2 ампера. Управлять такими удобней всего при помощи драйвера ШД — L6208. При работе на эту микросхему надо подавать лишь сигналы управления направлением движения, сигнал разрешения работы да тактовые импульсы. Контроллер сам регулирует ШИМ и устанавливает нужные напряжения на обмотках двигателя.

    Укажу пока основное, на что следует обратить внимание:
    • Регулировка ШИМа выполняется посредством сравнения падения напряжения на Sense-резисторах с опорным напряжением Vref. Поэтому для тока Imax и сопротивления резисторов RSense это падение можно рассчитать довольно просто:
      Uref = Imax · RSense
      Т.е. для выставления предельного тока в 1.2А при RSense=0.33Ом нужно задать Uref=0.4 В. Ни в коем случае нельзя оставлять ноги Vref висящими в воздухе или прижатыми к земле!
    • Режим Slow/Fast decay имеет значение для обычных коллекторных двигателей, шаговикам же Fast decay нужен лишь в режиме microstepping. В общем, если не извращаться, достаточно просто подать +5В на ножку CONTROL. На HALF/FULL тоже просто подаем +5В и работаем в полушаговом режиме. Аналогично поступаем с ногой RESET, если не хотим сбрасывать счетчик фаз (а его сбрасывать и не нужно, если честно отдавать на каждый шаг по 8 синхроимпульсов).
    • Вход ENABLE является еще и выходом: если с драйвером L6208 случается неприятность (перегрев, скачок тока), он самостоятельно отключает напряжение на нагрузке, а ENABLE подтягивает к земле. Это значит, что можно проверять, не случилась ли аварийная ситуация, если ногу контроллера, управляющую портом ENABLE, активировать в режиме выхода с открытым коллектором.
      По спецификации STM32, в режиме открытого коллектора при подаче единицы на выход порта просто запирается транзистор, подтягивающий ногу к земле. Если же на выход подать нуль, то нога опять подтягивается.
      Таким образом, подтянув ногу контроллера к +5В (ногу нужно выбирать FT) через, скажем, пятикилоомный резистор, а между ней и ENABLE воткнув, скажем, килоомный резистор (и обязательно не забыть шунтировать ногу ENABLE конденсатором на землю, иначе можно сжечь управляющий контроллер), можно и включать/выключать нужный двигатель, и проверять, не было ли аварий (а для этого можно повесить на соответствующие ноги контроллера прерывание периферии по спадающему фронту).
    • Проводники, помеченные в спецификации жирным, должны быть как можно короче и шире. Но при этом надо следить и за тем, чтобы уменьшить паразитные емкости и индуктивности.
      RSense должны располагаться как можно ближе к драйверу. Недалеко от них должны быть и конденсаторы C₁ и C₂ (по спецификации): через эту цепь течет не только ток сравнения, но и обратный ток индукции (поэтому, кстати, диоды в эту цепочку включать нельзя).
      Сигнальную землю соединять с силовой землей только далее точки подключения C₁ к земле, иначе ток индукции может попортить электронику просто падением напряжения на проводниках печатки (до меня, правда, не дошло, как это возможно, но лучше прислушаться к требованиям безопасности).
    • Еще я прочел интересную штуку: силовое питание нельзя подключать без сигнального! В документации рекомендуется даже брать напряжение 5 В от силового источника при помощи стабилизирующего блока.
      Я, честно говоря, ничего не понял: собственно +5 В подается лишь на управляющие ноги драйвера. Если мы не пользуемся двигателем, то, по идее, у него на всех сигнальных входах может быть 0. Но включать силовое питание таки буду после включения питания контроллера. И выключать в обратной последовательности. И следить, чтобы USB-шнурок, питающий контроллер, не выскочил...
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 21

      0
      Зачем выбрали сотую серию? Она уже постепенно сворачивается, а двухсотая имеет лучшие характеристики при той же цене. А вообще — лучше четырёхсотая, с DSP и плюшками, но она немного дороже.
        0
        Решил перейти с PICов на что-нибудь серьезное. Сотая серия сразу в глаза бросилась. А теперь уже поздно: макетка и контроллеры куплены.
        А на будущее думаю действительно что-нибудь поновее использовать: у 103 CAN и USB на одном таймере висят, что нехорошо (нельзя будет сделать железку, которая подключается к компьютеру по USB и управляет чем-нибудь по CAN). Да и ethernet хочется…
          0
          Двухсотая серия нога в ногу совместима. Сильно рекомендую на неё перейти.
            0
            Если понадобится-таки собрать переходник USB<->CAN «с преферансом и гимназистками», естественно, выберу более приличную модель.
              0
              Дык дело не моще, дело в том, что сотая серия не рекомендована для новых разработок.
                0
                Баги что ли какие в железе нашли?
                  0
                  Нет, просто прогресс ушёл. Сейчас ST рекомендую для новых разработок двухсотую и четырёхсотую серии. Они, кстати, более сбалансированные в плане архитектуры. И как говорил — совместимы нога-в-ногу с сотой серией.
            0
            Мы, кстати, контроллер в мате перепаяли — на пол часа делов.
        • UFO just landed and posted this here
            0
            Пока ещё не разобрался в статье, но заметил интересующий меня ньюанс: используется GPIO_SetBits().

            Правильно я понимаю, что эта функция позволяет управлять состоянием GPIO ног? Чья это функция (что за библиотека)? Не возникало ли с ней проблем? Какова её реальная скорость работы (временной лаг и т.п.)?
              +1
              Это — обычная обертка над GPIOx->BSRR = GPIO_Pin
              Пока нет нехватки ресурсов, можно пользоваться функциями библиотеки. А вообще, конечно, лучше по-человечески все делать. Но лень же, когда ресурсов хватает! (вот так у нас и получается, что каждая новая версия любого софта становится все более и более охочей до ресурсов: лень всему виной!)

              Библиотека — STDPeriphLib от ST. Проблем с ней не возникало. Задачи у меня не RT, поэтому я не парюсь о скорости работы (лаги в миллисекунды для моей задачи — не проблема). Конечно, если работать со звуком или видео, надо будет забыть про все эти библиотеки, а то и вообще на ассемблере писать.
                0
                Я правильно понимаю, что регистры GPIO доступны просто по адресу GPIOx->BSRR? А кто возвращает в линуксе этот указатель?
                  +1
                  Не совсем: у STM32 есть два регистра для записи данных в GPIO (советую почитать reference manual): 32-битный BSRR, позволяющий атомарно установить/сбросить биты порта, и 32-битный (но фактически используются лишь 16 младших бит) BRR, позволяющий атомарно сбросить биты порта.

                  Сама конструкция структуры порта определяется в заголовочном файле stm32f10x.h, идущем с STDPeriphLib, либо (если не пользоваться этой библиотекой) ее можно определить самому (по reference manual). От операционной системы, в которой код собирается, это, понятное дело, не зависит. gcc для ARM можно в любой операционке установить.
                    0
                    Спасибо за направление мысли. C STM32 я не работал. Но судя по коду, доступны 7 независимых портов, представленных регистрами по адресу:
                    #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)

                    #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)

                    И собственно пачка функций-оберток для работы с ними. Это моё предположение, даташит не читал.

                    Когда нет операционки, понятно, что работать напрямую можно и нужно. Но когда есть Линукс, неужто он любой программе дает копаться по этим адресам?
                      0
                      Дык, линукс здесь не при чем: нам нужно же как-то сказать компилятору об особенностях нашего контроллера — как минимум рассказать о расположении в памяти нужных для работы портов. Поэтому и используется соответствующий заголовочный файл.
                      Другой вариант — добавить заголовочные файлы для поддержки уймы железа в gcc. Однако, в arm-none-eabi аппаратнозависимых заголовочных файлов нет. Надо бы еще gcc-arm-none-eabi глянуть, как выше советовали. Но чувствую, там тоже такого не будет (а между тем, в sdcc было все необходимое для разработки под уйму PIC'ов).

                      А вот разработку клиентской части ПО в линуксе действительно будет удобнее делать. По крайней мере, просто сделав cat /dev/ttyACM0 вы уже сможете читать, что же там шлет контроллер на компьютер. Ну и при помощи libusb можно просто работать с железякой в userspace, не заморачиваясь с написанием модуля ядра.
                        0
                        У меня начинают появляться смутные сомнения, что фраза «программировать микроконтроллеры в линуксе» не обозначала гонять линукс на микроконтроллере… а описанный в статье код запускается сразу после бутлоадера…
                          0
                          Правильно. Если бы я говорил о линуксе на микроконтроллере (кстати, в 128..256кБ полноценный линукс ну никак не запихнуть), то так и говорил бы: «запускаем линукс на микроконтроллере».
                          А так я сказал «программировать микроконтроллеры в линуксе», т.е. писать для них код и прошивать их из-под линукса. Потому как примеров программирования микроконтроллеров из всяких коммерческих сред под винду — полным-полно, а линукс как-то обделили.
                          Несмотря на то, что сам код практически ничем не отличается во всех средах разработки, базовый Makefile нужно написать.
              0
              Добрый день.

              Пытаюсь повторить шаги этой статьи, но где-то застрял.
              Использую ubuntu 12.04, STM32F107, ST-LINK/V2.

              После установки arm-none-eabi тестовый проект stm32vldiscovery-linux-template стал собираться (получаю .elf файл). Дальше прошиваю его через через gdb и st-util, либо через st-flash, но лампочки не мигают.

              (gdb) load stm32vldiscovery-linux-template.elf
              Loading section .isr_vector, size 0x1d0 lma 0x8000000
              Loading section .text, size 0x18b0 lma 0x80001d0
              Loading section .init_array, size 0x4 lma 0x8001a80
              Loading section .fini_array, size 0x4 lma 0x8001a84
              Loading section .data, size 0x2c lma 0x8001a88
              Loading section .jcr, size 0x4 lma 0x8001ab4
              Start address 0x8000800, load size 6840
              Transfer rate: 6 KB/sec, 1140 bytes/write.
              (gdb) ?


              Или диоды распаяны на других ногах, или всё-таки что-то не так с прошивкой. Не понял из статьи, в каком месте используется objcopy, например. Буду рад помощи!
                0
                Прошивать надо не сам elf-файл, а пропущенный через objcopy (стадия make all состоит у меня из собственно сборки, objcopy, objdump и size).
                Я и сам поначалу с таким сталкивался. Советую внимательно мой Makefile почитать. А еще лучше — для начала целиком мой проектик собрать и прошить (make && make load).
                Для прошивки используется st-flash.

                Еще, конечно, важно посмотреть схему макетки, т.к. на разных макетках обвес разный, да и подключен он к разным ногам (тех же USART'ов у STM32F107 целых 4 штуки!).
                  0
                  Спасибо за помощь. Я взял за образец один из мейкфайлов по ссылкам, а не ваш проект, и там не было целей с objcopy. Плюс на моей отладочной плате светодиоды висят на GPIOE.
                  Ещё видел какой-то более не воспроизводимый баг, когда после записи программы она не запускалась, а при вызове дебага — запускалась.
                  От безысходности пробовал также запуститься на windows по этому руководству, теперь вернулся мигать диодами на свою чёрную-чёрную консоль.
                0
                UPD: поигравшись с библиотекой libopencm3, я решил перейти с STM'овских велосипедов на нее. Обновленную версию проекта разместил в отдельной директории, чтобы не «ломать» всю структуру.
                Скоро уже придет железо для проекта. Надеюсь, к концу года уже будет кое-какой «настоящий» рабочий код, а не тестирование отдельных модулей.

                Only users with full accounts can post comments. Log in, please.