Светодиоды, кнопки и Bluetooth hc-05. Часть 01

Как то так получилось, что самый дешевый bluetooth (HC-04, HC-05 ..) модуль стоит 5$, позиционируется он исключительно как преобразователь bluetooth -> com. (SPP), Но ведь в нем есть процессор, для него есть компилятор и IDE, почему бы не воспользоваться всеми благами HC-05.
Статья пишется исключительно как напоминалка самому себе, из-за чего стиль может существенно отличаться. Да и так как некоторых вещей я и сам не понимаю, из-за некоторого отсутствия описания, они будут описаны как просто рабочие, приглашаю всех к дополнению и исправлению. Хотя, господа, нет лучшего способа научится самому — чем научить кого-нить другого

Перед прочтением желательно ознакомится с вот этими ссылками:


После того как вы знаете как его программировать (см. ссылки), можно смело запускать xIDE, и начинать кодить.

Часть 0 VM, структура программы, процессы, сообщения…
Как и любая другая программа на Си, здесь все точно также, — препроцессор функции, итд. Но для того, чтобы не мешать bluetooth выполнять свои действия и своевременно реагировать на системные вызовы (события) здесь прилагается еще мелкая невытесняющая многозадачная ОС, т.е пока не закончили предыдущий процесс следующий не начнется. Обмен данными между процессами (task)выполняется с помощью сообщений (messages). За это все дело отвечает библиотека messages.h

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


typedef struct _comp_task_data 
{
    TaskData    cmd_task;
    char      comp;
} CompTaskData;


Предположим задача процесса сравнивать два числа из предыдущего сообщения и последующего, и включать соответствующий PIO порт, если значение из сообщения больше — Pio1, если меньше Pio0. Хотя Pio это уже другая история.


static void Comp_handler(Task t, MessageId id, Message payload)
{   
    CmdTaskData *ctd = (CmdTaskData *) t;
    if(payload > ctd->comp)
    {
        PioSet(1,1); //включить первый светодиод, выключить нулевой.
        PioSet(0,0);
    }
    else
    {
        PioSet(0,1); //включить 0 светодиод, выключить 1.
        PioSet(1,0);
    }
    ctd->comp= payload;
    MessageSend(t,0,payload+1); // Это то из за чего всегда будет гореть первый светодиод, отправляем этой же задаче сообщение.
}


Фактически с сообщениями все, осталось создать сам процесс, и отправить ему сообщение.


Static CompTaskData comp_task = 
{
    { Comp_handler }, // <- Указатель на функцию процесса, обработчика
    0                   // начальные данные переменной comp
};
   
int main(void)
{
    MessageSend(
            &comp_task.Comp_task,     /* Task the message is sent to */ 
            0                         /* Message Id */
            1 );                        /* Message Payload */
    
    MessageLoop();

    return 0; 
}


Это все конечно хорошо, но это можно прочитать и в [2] местами даже более детально.
Теперь о том что есть еще интересного в передаче сообщений.
Функция:

 void MessageSendLater 	(  	Task  	task,
		MessageId  	id,
		void *  	message,
		uint32  	delay
	)  


Отправляет сообщение через delay миллисекунд. типичное применение «мигание светодиодом»

Пример:

typedef struct _ledflash_task 
{
    TaskData    led_task;
} LedTaskData;


static void ledflash(Task t, MessageId id, Message payload)
{
    PioSet(0,payload);
    SendMessageLater(t,0,¬payload,500) //<- отправит сообщение с инвертированным через 500мс.
}

Большинство вкусностей связанных с сообщениями описано в doc/reference/html/message_8h.html относительно корня bluelab.


Вообще почти все примеры в связи с таким применением многозадачность строятся подобно цифровым автоматам, На примере шагового двигателя с графом переходов 0001->0010->0100->1000 выглядит это както-так:

#define StepZero 0x1
#define StepOne 0x2
#define StepTwo 0x4
#define StepThree 0x8


static void apptask(Task task, MessageId id, Message message)
{
	switch(id)
		case StepZero: 
			{
			PioSet(0x0f,id);
			MessageSendLater(t,StepOne,0,5);
			}
			break;
		case StepOne:
			{
			PioSet(0x0f,id);
			MessageSendLater(t,StepTwo,0,5);
			}
			break;
		case StepTwo:
			{
			PioSet(0x0f,id);
			MessageSendLater(t,StepThree,0,5);
			}
			break;
		case StepThree:
			{
			PioSet(0x0f,id);
			MessageSendLater(t,StepZero,0,5);
			}
			break;
		default: MessageSend(t,StepZero,0);
			break;
}




Часть 1, PIO
PIO — это просто, но не быстро. Так как оказалось процессор(XAP архитектуры) в этом модуле работает всего на 16мгц, и не известно сколько у него мипсов, а мне мерять лень… Одним словом, это не быстро, сдается мне, что кроме индикации и кнопок эти порты больше ни на что не проектировались

Напоминаю что PIO это тоже что GPIO и тоже что и «Порты Ввода/Вывода». Для управления оными нам дается библиотека pio.h
Начиная с простого, функция PioGet() считывает значение с порта.

uint16 i = PioGet();


PioSet наоборот устанавливает значение, первым аргументом к порту служит маска пина (например 0х6 -> 4 | 2 -> sqrt(4), sqrt(2) -> ((1<<1)|(1<<2)), т.е первый и второй пин). второй аргумент — значение. код:

PioSet(0xfff,0xfff); //12bit -> 0xfff 

Выставит в 1 весь порт.

Направления порта регулируются следующим функциями:

PioSetDir(uint16 mask, uint16 dir);
uint16 t = PioGetDir();

где маска — это маска, см. выше.
dir — направление где 0 вход 1 выход.
Все точно также как и в AVR с DDR регистром
Также на каждый порт можно включить подтяжку резистором, делается это все через

PioSetStrongBias(mask,bits);
uint16 t = PioGetStrongBias();


Следующий код показывает, как можно безболезненно помигать светодиодиком подключенным к нулевому пину.


#include <message.h>
#include <pio.h>		/* Peripheral Input/Output */

static void led( Task t, MessageId id, Message payload )
{
	PioSet( 1, (PioGet() ^ 1) );
	MessageSendLater( t, 0, 0, 500 );
}


static TaskData led_task = { led };

int main(void)
{
	PioSetDir(0xFF, 0xFF);         
	PioSet(0xFF, 0);              
	
	MessageSend( &led_task, 0 , 0 );
	MessageLoop();
	
	return 0;
}


Вариантов «помигать светодиодиком» даже в примерах предлагается аж 3 шутки. Кроме выше перечисленных функций некторые bluecore умеют мигать светодиодиками через специальные функции PioSetLed0, PioSetLed1, PioDimLed0,PioDimLed1… но с данными модулями нам это не грозит.

Для любителей помигать светодиодиками есть еще одна «фича» — паттерны. т.е заготовки с описанием как именно мигать светодиоду.
паттерны собираются с помощью отдельного приложения ledparse.exe, оно есть в комплекте, находится в папке tools\bin, или просто создав LED-файл в проекте.


паттерн выглядит следующим образом:

pio 0 LED1
pio 1 LED2

pattern PATTERN1 RPT
    LED1 ON  200
    LED1 OFF 0
    LED2 ON  200
    LED2 OFF 0


первая строчка описывает, что имя led1 присвоено пину 0, led2 пину 1
далее идет слово pattern [название] RPT (repeat, повторять паттерн)
Вообщем-то ledparse выдает довольно подробную информацию про паттерны. Собирается это дело ledparse [in] [out] -> ledparse patt.led patt. на выходе создадутся два файла patt.c и patt.h, с другой стороны в xide подключаемые файлы создаются автоматически.

без слова rpt данная комбинация проиграется всего один раз.
Дальше, соответственно включить LED 1 на 200мс, выключить ну итд…

В программе все это работает после подключение patt.c и patt.h файлов. Вызов паттерна же выполняется через ledplay функцию с указанием имени паттерна, ниже полный код этого дела.


#include <message.h>
#include <pio.h>
#include "patt.h"

int main(void)
{
    ledsPlay(PATTERN1);
    MessageLoop();
    return 0;
}


Теперь пора перейти к кнопочкам
Если с PioGet, и PioSetStrongBias все ясно то вот с PioDebounce нет. Функция передает сообщение процессу в случае изменения состояния пина. Процесс которому это передается указывается не в функции а через другую функцию, которая фактически, инициализирует процесс MessagePioTask.

Сам PioDebounce принимает следующие аргументы:

void PioDebounce (uint16 mask, uint16 count, uint16  period)

Маска как всегда остается маской.
Count — количество раз которое должна быть прочтена кнопка. Дело в том, что для устранения дребезга контактов, в этой функции, кнопку считывает count раз через period времени (в мс). Если во всех случая получены одни и те же результаты, то функция передаст сообщение MESSAGE_PIO_CHANGED процессу указанному через MessagePioTask. В сообщение также передаются данные о изменение порта (state), времени (time) как долго это состояние продолжалось (?), и состояние остальных портов с 16 по 32 (state16to31) если они есть, иначе заполняются 0-ми.
Зная это все программа должна выглядить следующим образом:



#include <pio.h>
#include <message.h>

typedef struct
{
    TaskData    task;   
} appState;

static void app_handler(Task task, MessageId id, Message message) 
{
    switch (id)
    {
    case MESSAGE_PIO_CHANGED:
        {
            MessagePioChanged *pio = (MessagePioChanged*)message;
            if (pio->state & (1>>2)) PioSet(1<<0,1<<0);
            if (pio->state & (1>>3)) PioSet(1<<1,1<<1);    
        }
        break;
    }
}

static appState app = {{app_handler}};


int main(void)
{
    MessagePioTask(&app.task);
    PioDebounce((1<<2) | (1<<3),    2, 20);                 
    MessageLoop();   
    return 0;
}


Точно также как для индикации есть паттерны, патерны есть и для кнопок, входа. Генерируются с помощью buttonparse.exe который можно найти все там же, или просто добавив в проект button-файл.

Формат паттерна следующий:

debounce 2 20

pio 0 BUTTON_A
pio 1 BUTTON_B
pio_raw 2
pio_raw 3

message A_PRESS
    BUTTON_A    enter

message B_RELEASE
    BUTTON_B    release

message A_DOUBLE
    BUTTON_A    double 500


Первая строчка указывает, что для того чтобы было отправлено сообщение надо 2 раза в промежутке 20мс поймать нажатие. (ака Pio_debounce)
Дальше идут назначения, на 0 пин именуется кнопкой А, 1 -> B, 2,3 идут без обработчика (без debounce, фильтра дребега контактов)
Дальше слово message указывает что за ним идет типизированное название передаваемого сообщения в программу, дальше будет ясно о чем идет речь. Со следующей строки имя пина и действие, хотя возможно я перечислю не все действия, но все которые мне известны.
  • enter -> нажатие, вызывается по нажатию на кнопку и прохождению подавления дребезга контактов
  • release -> отпускание
  • double [time] — двойное нажатие через time мс
  • held [time] — удерживание на time мс
  • held_release [time] — отпускание после удерживания time времени.
  • held [time] -> repeat [time] — вызывать каждый time интервал если кнопка все еще нажата, для этого отдельный пример:

message B_HELD
    BUTTON_B    held 1000
    BUTTON_B    repeat 1000

Отправлять каждую секунду новое сообщение если кнопка все еще нажата.
В программе это оформляется следующим образом, показан процесс, а именно обработчик сообщений из примеров.


static void app_handler(Task task, MessageId id, Message message) 
{
    switch (id)
    {
    case AB_HELD:
        printf("Buttons A and B held 1s\n");
        break;

    case A_HELD_RELEASE:
        printf("Button A released after held over 3s\n");
        break;

    case B_HELD:
        printf("Button B held 1s\n");
        break;

    case PIO_RAW:
        handle_pio_raw(task, (PIO_RAW_T*)message);
        break;
        
    default:
        printf("Unhandled message 0x%x\n", id);
    }
}

По поводу отладки, это отдельная песня, необходимо не использовать printf без отладчика/программатора.
В основной функции для инициализации «кнопок» используется функция

 pioInit(&app.pio, &app.task);

Кроме того структура описывающая процесс должна содержать переменную PioState pio;

typedef struct
{
    TaskData    task;   /* task is required for messages to be delivered */
    PioState    pio;    /* PIO state used by buttonparse */
} appState


Полный текст программы можно найти в tuttorial -ах button, part2,part3,part4.
PIO_RAW_T. имеет в себе как минимум одно следующее свойство pio; Которое показывает состояние порта.


static void  handle_pio_raw(Task task, PIO_RAW_T *raw)
{
 printf("PIO  = %x\n", raw->pio);
}
  • +7
  • 07 января 2013, 23:59
  • letni

Комментарии (20)

RSS свернуть / развернуть
Там реально заставить работать DTR, RTS? А ШИМ выводить на пины?
0
DTR, RTS да, ШИМ нет, только в 5-х корках.
Китайцы для своего модуля просто собрали BlueLab41\apps\examples\spp_dev_a пример, хотя я до него еще особо сильно не доехал.
0
Скомпилишь прошивку для беспроводной прошивки атмег?
0
бутлодирь->уарт->блютуз->аврпрог не взлетит?
0
да возьмите пример и перекомпильте, туда все что угодно влезит. Обещанная скорость того же rfcomm 70кбит.
0
Скомпилить мб я и скомпилю, но мне попросту будет лень разбиратся в ISP AVR, да и в самом блютуз тоже. Описания на него почти нету. Вообще у меня немного другая задача была, удаленно с помощью rfcomm помигать светодиодом да опросить кнопку. У меня это получилось, но он либо мигает либо опрашивает.
0
Со светодиодами не понял.
без слова rpt данная комбинация проиграется всего один раз.
Дальше, соответственно включить LED 1 на 200мс, выключить ну итд…
static void led_controller1( Task t, MessageId id, Message payload )
{
    
    ledsPlay(PATTERN1);
     MessageSendLater( t, 0, 0, 400 );
}
Как-то не стыкуются. Если указан rpt, то зачем вызов MessageSendLater?
0
моя ошибка, поправил. Здесь можно обойтись и без создания процесса, так как ledplay создает новый и при каждом вызове убивает старый процесс.
0
а как через АТ команду пин включить?
например добавить АТ команд
АТ SETPIO b.n
где b -Номер бита
n-0 или 1
0
Искал в файлах IDE по разным словам
которые есть в списке команд модулей — ничего не находит…
Может как-то иначе надо искать.
0
или просто по отправке текста pio2
0
нашёл
pfalcon-oe.blogspot.ru/2012/04/opensource-sensor-node-firmware-for.html?m=1
тут всё есть для того чтобы использовать модули автономно.
0
Зимой игрался с этими штучками.
Даже написал целую серию статей «Как приручить свой Bluetooth».
-1
Подскажи линк.
0
Дык ить вот — https://zhevak.wordpress.com/2015/12/04/как-приручить-свой-bluetooth-часть-1/

А вообще гуглится по строке «Как приручить свой Bluetooth» тоже не плохо.
+1
работа большая но я не увидел ни одного слова про автономную работу модуля, компиляцию, функции, CSR SDK GPIO и т.д.
просмотрел 8 глав текста, картинок и философии.
+1
А я ничего и не обещал про «автономную работу модуля», я просто поделился своими наработками. Не хочешь — не ешь, делов-то!
-1
На днях нарыл допил библиотеки от CSR для отладки и программирования через USB FT232RL включая подделки.
github.com/lorf/csr-spi-ftdi
0
Честно, уже не помню как там чего делается. Да и вы судя по всему зашли даже дальше чем я, в чем вы конечно молодец. Отпишитесь о проделанной работе, будет интересно почитать.
0
так же увидел что некий человек (Maxl) собрал ту прошивку и сделал...
forum.easyelectronics.ru/viewtopic.php?p=345891#p345891
предлагал готовый дамп но никто чёт не захотел воспользоваться его трудом.
Написал ему письмо.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.