Здравствуйте.
Сегодня хотел бы рассказать Вам о том, как можно связать отладочную плату STM32 F3 DISCOVERY с телефоном на базе ОС Android. Данная статья не будет уроком, Я лишь поделюсь своими наработками в достаточно фривольной форме.
Недавно передо мной встала задача передачи данных АЦП с платы STM32 F3 DISCOVERY на телефон под управлением системы Android. Решено было организовать передачу при помощи Bluetooth, так как он как раз имелся на борту телефона, и модуль HC-05 был под рукой)
Питание для Bluetooth модуля можно смело брать с отладочной платы, так как его потребление колеблется в районе 50мА. Для отладки устройства, на вход АЦП подавался сигнал с потенциометра. В итоге получилась следующая схема:
UART1 находится на ножках PA9(TX) и PA10(RX). Поэтому ножка PA9 подключается к ножке RX модуля. На ножку PC1 подадим напряжение с потенциометра R1, изменяя которое будем проверять корректность передаваемых данных. Резистор R2 необходим для защиты цепи, когда потенциометр R1 будет занимать крайние положения.
Телефон(или даже планшет) подойдет почти любой. Требуется лишь версия Андроид старше 4.0.3 (Хотя без проблем запустилось на HTC Desire HD, с Android 2.3.5) и наличие Bluetooth.
И вот тут первая незадача, все попытки инициализировать UART отладочной платы при помощи SPL, закончились неудачами. Данные либо вовсе не шли, либо шел какой то мусор.
Поэтому решено было настраивать UART при помощи ручной правки регистров (привет CMSIS!). На этом проблемы с написанием прошивки для STM32F3DISCOVERY закончились, и код приобрел свой окончательный вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#include "main.h" #include "usart.h" #include "adc.h" uint16_t result; void Init_RCC() { RCC_HSEConfig(RCC_HSE_ON); //Enable HSE while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET) ; //Waiting for HSE //Set Flash latency FLASH->ACR |= FLASH_ACR_PRFTBE; FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)((uint8_t)0x02); RCC_PREDIV1Config(RCC_PREDIV1_Div1);//PREDIV 1 Divider = 1 RCC_PLLConfig(RCC_PLLSource_PREDIV1,RCC_PLLMul_9);//Set PREDIV1 as source for PLL,And set PLLMUL=9 RCC_PLLCmd(ENABLE);//Enable PLL while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) ;//Waiting for PLL RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//Set PLL as SYSCLK Soucre RCC_HSICmd(DISABLE);//Disable HSI } void Init_Timer() { TIM_TimeBaseInitTypeDef timer2; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); TIM_TimeBaseStructInit (&timer2); timer2.TIM_Prescaler = 7200; timer2.TIM_Period = 10000; TIM_TimeBaseInit (TIM2,&timer2); NVIC_EnableIRQ(TIM2_IRQn); TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); TIM_Cmd(TIM2,ENABLE); } void ADC1_2_IRQHandler(void) { if(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)) { result = ADC_GetConversionValue(ADC1); ADC_ClearITPendingBit(ADC1,ADC_IT_EOC); } } void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET) { char buffer[16]; sprintf(buffer,"%d\n",result); Usart1_Send_String(buffer); STM_EVAL_LEDToggle(LED10); TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } } int main (void) { Init_RCC(); usart_init(); __enable_irq(); STM_EVAL_LEDInit(LED10); Init_ADC(); Init_Timer(); while(1) { } } |
Рассмотрим данный код подробнее.
Сначала необходимо настроить RCC, подробнее об этом читайте здесь. После этого в функции usart_init настраиваем USART/UART1 отладочной платы.
Код функций UART/USART (Файл usart.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include "stm32f30x_gpio.h" #include "stm32f30x_rcc.h" #include "stm32f30x_syscfg.h" #define F_CPU 72000000 #define BAUDRATE 9600 void usart1_tx(uint8_t c){ while (!(USART1->ISR & USART_ISR_TXE)); USART1->TDR = c; } void usart_init(void){ RCC->AHBENR |= RCC_AHBENR_GPIOAEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; GPIOA->MODER &= ~GPIO_MODER_MODER9_0; GPIOA->MODER |= GPIO_MODER_MODER9_1; GPIOA->AFR[1] |= 7 << 4*1; GPIOA->MODER &= ~GPIO_MODER_MODER10_0; GPIOA->MODER |= GPIO_MODER_MODER10_1; GPIOA->AFR[1] |= 7 << 4*2; USART1->BRR = F_CPU/BAUDRATE; USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } void Usart1_Send_String(char* str) { uint8_t i=; while(str[i]) { usart1_tx(str[i]); i++; } } |
В функции usart_init(void) мы настраиваем USART1/UART1 нашего микроконтроллера. Для этого мы сначала включаем его тактирование, затем настраиваем соответствующие выводы, задаем скорость передачи данных, и напоследок включаем его. Функции usart1_tx и Usart1_Send_String , служат для передачи по UART одного символа и строки соответсвтенно.
После настройки UART, остается лишь настроить АЦП и таймер, так как передавать значение АЦП мы будем раз в секунду. О настройке таймера можете прочесть здесь а о настройке АЦП в данном уроке.
Для проверки работы можно использовать программу Bluetooth Terminal для телефона. Однако, в мою задачу так же входило написание приложения для телефона, которое предоставляло бы данные пользователю в удобочитаемом виде.
Писать ПО для ОС Android предлагаю в официальной среде Android Studio. Подробнее о ней мы поговорим в одной из следующих статей нашего нового раздела мобильные приложения. Пока лишь покажу вам код текущего проекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
package damikk.bluetooth; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.bluetooth.* ; import android.view.* ; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.os.Handler ; import com.jjoe64.graphview.GraphView; import com.jjoe64.graphview.series.DataPoint; import com.jjoe64.graphview.series.LineGraphSeries; import java.io.IOException ; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID ; import java.lang.StringBuilder ; public class MainActivity extends AppCompatActivity { BluetoothAdapter bluetooth; BluetoothDevice btDevice; BluetoothSocket clientSocket; private static String btDeviceAddress = "98:D3:32:70:AC:AA"; StringBuilder sb = new StringBuilder(); private ConnectedThread mConnectedThread; Handler h; TextView txtView ; TextView txtView2; ProgressBar progressBar; LineGraphSeries <DataPoint> series; int CurrentStep = ; private class ConnectedThread extends Thread { private BluetoothSocket mmSocket; private InputStream mmInStream; private OutputStream mmOutputStream; public ConnectedThread (BluetoothSocket socket) { mmSocket = socket; InputStream inStream = null; OutputStream outStream = null; try { inStream = socket.getInputStream(); outStream = socket.getOutputStream(); } catch (IOException e) { Toast toast = Toast.makeText(getApplicationContext(), "Error reading stream:"+e.getMessage(), Toast.LENGTH_SHORT); toast.show(); } mmInStream = inStream; mmOutputStream = outStream; } public void run() { byte [] buffer = new byte[255]; int bytes; while(true) { try { bytes = mmInStream.read(buffer); h.obtainMessage(1,bytes,-1,buffer).sendToTarget(); } catch (IOException e) { Toast toast = Toast.makeText(getApplicationContext(), "Error reading stream:"+e.getMessage(), Toast.LENGTH_SHORT); toast.show(); } } } public void cancel() { try { mmSocket.close(); } catch (IOException e) {} } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txtView = (TextView) findViewById(R.id.textView); txtView2 = (TextView) findViewById(R.id.textView2); progressBar = (ProgressBar) findViewById(R.id.progressBar); h=new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case 1: byte[] buffer = (byte[]) msg.obj; String strIncome = new String(buffer,,msg.arg1); sb.append(strIncome); int EndOfLine = sb.indexOf("\n"); if(EndOfLine >) { String res = sb.substring(,EndOfLine); sb.delete(,sb.length()); int num = Integer.parseInt(res); progressBar.setProgress(num); String tmp = Integer.toString(num); txtView2.setText("Current:"+tmp+"/4095"); } break; } } }; } public void ShowMsg(String error) { Toast toast = Toast.makeText(this, error, Toast.LENGTH_SHORT); toast.show(); } public void BtnConnect_clicked (View v) { bluetooth = BluetoothAdapter.getDefaultAdapter(); if (bluetooth == null) { ShowMsg("Bluetooth is empty!"); return; } if (bluetooth.isEnabled() == false) { ShowMsg("Bluetooth is not enabled!"); return; } btDevice = bluetooth.getRemoteDevice(btDeviceAddress); try { UUID uuid = btDevice.getUuids()[].getUuid(); clientSocket = btDevice.createRfcommSocketToServiceRecord(uuid); } catch (IOException e) { ShowMsg("Connection Error:"+e.getMessage()); return; } bluetooth.cancelDiscovery(); try { clientSocket.connect(); } catch (IOException e) { ShowMsg("Connection Error:" +e.getMessage()); return ; } txtView.setText("Connected to:"+ btDevice.getName()); mConnectedThread = new ConnectedThread(clientSocket); mConnectedThread.start(); } } |
Полностью объяснять данный код считаю нецелесообразным, так как он достаточно сырой, и делался по принципу “лишь бы заработало уже”. Однако при небольшой доработке (которую я скорее всего произведу в ближайшее время) данное приложение подойдет для работы с Bluetooth устройствами на микроконтроллерах.
Пока лишь остановимся на моментах, вызвавших у меня затруднения.
Необходимо получить адрес нашего модуля. Сделать это помогает указанная выше программа Bluetooth Terminal. Достаточно найти модуль HC-05 в данной программе, а адрес будет написан рядом с названием модуля. Для тех, кто не в курсе, адрес выглядит примерно так:
98:D3:32:70:AC:AA
После того как Вы нашли данный адрес, не забудьте поменять соответствующую строку программы:
1 |
private static String btDeviceAddress = "ВАШ АДРЕС"; |
И ещё один момент.
Работать с Bluetooth в Android следует в отдельном потоке. Для этого служит класс ConnectedThread. А для отображения принятых данных, следует использовать класс Handler. Сначала это кажется слегка запутанным, однако потратив немного времени на изучение кода, Вы обязательно все поймете.
Внешний вид программы получился таким:
Отображение графика пока не реализовано.
По мере вращения ручки потенциометра R1 изменяется получаемое телефоном значение (0..4095) в соответствии с которым заполняется ProgressBar.
В процессе отладки устройство выглядело так:
В принципе задачу можно считать реализованной. Однако недоработок ещё куча, так как это был лишь мой первый опыт работы с ОС Android. Второй опыт смотрите здесь.
Надеюсь что Вам был интересен мой опыт. Напомню, что данная статья лишь описывает личный опыт автора, и не является уроком. Весь код представленный на данной странице не лишен ошибок, однако он вполне работоспособен.