Здравствуйте! Представляю вашему вниманию проект обмена данными между Android-приложением и SoC nRF52832 через последовательный порт.
Содержание статьи / Table Of Contents
↑ Видео-демонстрация проекта
↑ Код для nRF52832
В проекте использована макетная плата nRF52832, в состав которой входят светодиод (вывод P0.17) и кнопка (вывод P0.14).Проект, кроме main.c, содержит три библиотеки.
Библиотека Serial обеспечивает:
• инициализацию протокола UART со скоростью 9600 bps и разрешение прерывания по факту приёма данных,
• отправку байта данных,
• приём байта данных с сохранением в переменной receivedData;
Serial.h
#ifndef SERIAL_H_
#define SERIAL_H_
#include "nrf.h"
#define TX_PIN 6
#define RX_PIN 8
void serialBegin();
void serialSendByte(char data);
extern volatile unsigned char receivedData;
#endif /* SERIAL_H_ */
Serial.c
#include "Serial.h"
volatile unsigned char receivedData = 0;
void UART0_IRQHandler()
{
while(!NRF_UART0->EVENTS_RXDRDY);
NRF_UART0->EVENTS_RXDRDY = 0;
receivedData = NRF_UART0->RXD;
}
void serialBegin()
{
NRF_GPIO->PIN_CNF[TX_PIN] = GPIO_PIN_CNF_DIR_Output << GPIO_DIR_PIN11_Pos;
NRF_UART0->BAUDRATE = UART_BAUDRATE_BAUDRATE_Baud9600 << UART_BAUDRATE_BAUDRATE_Pos;
NRF_UART0->PSELTXD = TX_PIN;
NRF_UART0->PSELRXD = RX_PIN;
NRF_UART0->INTENSET = UART_INTENSET_RXDRDY_Enabled << UART_INTENSET_RXDRDY_Pos;
NVIC_EnableIRQ(UARTE0_UART0_IRQn);
NRF_UART0->ENABLE = UART_ENABLE_ENABLE_Enabled << UART_ENABLE_ENABLE_Pos;
NRF_UART0->TASKS_STARTTX = 1UL << 0;
NRF_UART0->TASKS_STARTRX = 1UL << 0;
}
void serialSendByte(char data)
{
NRF_UART0->TXD = data;
while(!NRF_UART0->EVENTS_TXDRDY);
NRF_UART0->EVENTS_TXDRDY = 0;
}
В с-файле библиотеки button настраивается прерывание по обоим фронтам сигнала на входе P0.14, в обработчике которого :
• устанавливается в 1 значение переменной buttonFlag,
• прерывание запрещается до истечения времени дребезга кнопки.
В файле button.h оформлены макросы:
• разрешения/запрета прерывания,
• опроса регистра IN с целью определить, отпущена ли кнопка.
button.h
#ifndef BUTTON_H_
#define BUTTON_H_
#include "nrf.h"
#define BUTTON 14
#define BUTTON_RELEASED NRF_GPIO->IN & (1UL << BUTTON)
#define BUTTON_ON NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN0_Enabled << GPIOTE_INTENSET_IN0_Pos;
#define BUTTON_OFF NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN0_Disabled << GPIOTE_INTENSET_IN0_Pos;
#define BUTTON_BOUNCE_TIME 30
void buttonInit();
extern volatile unsigned char buttonFlag;
#endif /* BUTTON_H_ */
button.c
#include "button.h"
volatile unsigned char buttonFlag = 0;
void GPIOTE_IRQHandler()
{
while(!NRF_GPIOTE->EVENTS_IN[0]);
NRF_GPIOTE->EVENTS_IN[0] = 0;
BUTTON_OFF;
buttonFlag = 1;
}
void buttonInit()
{
NRF_GPIO->PIN_CNF[BUTTON] = (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos);
NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) |
(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) |
(GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) |
(BUTTON << GPIOTE_CONFIG_PSEL_Pos);
BUTTON_ON;
NVIC_EnableIRQ(GPIOTE_IRQn);
}
В файле main.c:
1. Функция systemInit() настраивает таймер TIMER0 на генерацию прерывания раз в миллисекунду. В обработчике прерывания устанавливается в 1 значение переменной clockFlag.
2. В основной функции осуществляется начальная инициализация модулей МК, а затем в цикле опрашиваются значения переменных receivedData и clockFlag и:
а) Включается/выключается светодиод при значениях „1“/“2“ переменной receivedData, соответственно. При этом, сама переменная обнуляется.
б) Один раз в миллисекунду проверяется значение buttonFlag и, если оно равно 1, инкрементируется переменная clockCounter, отсчитывающая время дребезга (30 мс). По истечению последнего buttonFlag и clockCounter обнуляются и в зависимости от текущего состояния кнопки (нажата/отпущена) в последовательный порт отправляется соответствующий символ (“1”/”2”).
main.c
#include "Serial.h"
#include "led.h"
#include "button.h"
volatile unsigned char clockFlag = 0;
unsigned char clockCounter = 0;
void TIMER0_IRQHandler()
{
NRF_TIMER0->EVENTS_COMPARE[0] = 0;
clockFlag = 1;
}
void SystemInit()
{
NRF_TIMER0->PRESCALER = 6;
NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
NVIC_EnableIRQ(TIMER0_IRQn);
NRF_TIMER0->TASKS_START = 1;
}
int main(void)
{
serialBegin();
ledInit();
buttonInit();
while (1)
{
if(receivedData)
{
if(receivedData == '1')
LED_ON;
else if(receivedData == '2')
LED_OFF;
receivedData = 0;
}
if(clockFlag)
{
clockFlag = 0;
if(buttonFlag)
{
clockCounter++;
if(clockCounter == BUTTON_BOUNCE_TIME)
{
clockCounter = 0;
buttonFlag = 0;
if(BUTTON_RELEASED)
serialSendByte('2');
else
serialSendByte('1');
BUTTON_ON;
}
}
}
}
}
↑ Android-приложение
Приложение написано на языке Java в Android Studio.В файле xml-файле приложения прописаны параметры элементов графического интерфейса — кнопок соединения с последовательным портом и включения/выключения светодиода, а также текстового поля, отражающего состояние кнопки на макетной плате nRF52832.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/connectBtn"
android:layout_width="100dp"
android:layout_height="80dp"
android:onClick="openSerialPort"
android:text="connect"
android:textSize="30px"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.023" />
<Button
android:id="@+id/ledOnBtn"
android:layout_width="100dp"
android:layout_height="80dp"
android:onClick="ledOn"
android:text="led on"
android:textSize="30px"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.024" />
<Button
android:id="@+id/ledOffBtn"
android:layout_width="100dp"
android:layout_height="80dp"
android:onClick="ledOff"
android:text="led off"
android:textSize="30px"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.949"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.023" />
<TextView
android:id="@+id/textView"
android:layout_width="366dp"
android:layout_height="64dp"
android:onClick="ledOff"
android:text="Button is released"
android:textAlignment="center"
android:textColor="#143215"
android:textSize="110px"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.488"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.215" />
</androidx.constraintlayout.widget.ConstraintLayout>
В MainActivity Java-файла оформлены обработчики нажатия кнопок графического интерфейса:
1. ledOn() передаёт в порт символ “1”,
2. ledOff() передаёт в порт символ “2”,
3. openSerialPort():
• обеспечивает соединение с последовательным портом на скорости 9600 bps,
• запускает отдельный поток, в котором при поступлении от nRF52832 символов “1” или “2” соответствующим образом меняется содержимое текстового поля.
MainActivity.java
package com.example.androidserial;
import androidx.appcompat.app.AppCompatActivity;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private UsbManager mUsbManager;
private UsbSerialPort mSerialPort;
private UsbDeviceConnection mConnection;
private UsbSerialDriver driver;
private Button connnect, ledOnBtn, ledOffBtn;
private TextView mTextView;
private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
private enum UsbPermission { Unknown, Requested, Granted, Denied }
private UsbPermission usbPermission = UsbPermission.Unknown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connnect = findViewById(R.id.connectBtn);
ledOnBtn = findViewById(R.id.ledOnBtn);
ledOffBtn = findViewById(R.id.ledOffBtn);
mTextView = findViewById(R.id.textView);
mTextView.setTextColor(Color.BLACK);
}
public void openSerialPort(View view) {
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager);
if (availableDrivers.isEmpty()) {
Toast.makeText(this, "No serial device found!", Toast.LENGTH_SHORT).show();
} else {
driver = availableDrivers.get(0);
mSerialPort = driver.getPorts().get(0);
mConnection = mUsbManager.openDevice(driver.getDevice());
}
if (mConnection == null && usbPermission == UsbPermission.Unknown && !mUsbManager.hasPermission(driver.getDevice())) {
usbPermission = UsbPermission.Requested;
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(INTENT_ACTION_GRANT_USB), flags);
mUsbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
return;
}
try {
mSerialPort.open(mConnection);
mSerialPort.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
Toast.makeText(this, "connected", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Toast.makeText(this, "Error opening device: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
new Thread(new Runnable() {
public void run() {
while (true) {
try {
byte[] buffer = new byte[3];
mSerialPort.read(buffer, 100);
if (buffer[0] == '1') {
mTextView.setText("Button is pressed");
} else if (buffer[0] == '2') {
mTextView.setText("Button is released");
}
} catch (IOException e) {
}
}
}
}).start();
}
public void ledOn(View view) {
try {
byte[] data = "1".getBytes();
mSerialPort.write(data, 2000);
} catch (Exception e) {
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
public void ledOff(View view) {
try {
byte[] data = "2".getBytes();
mSerialPort.write(data, 2000);
} catch (Exception e) {
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
}
↑ Файлы
🎁Все коды и проект из статьи в zip 19.03 Mb ⇣ 29Благодарю за внимание!
Продожнение следует.
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.