§ 31. WoodmanUSB. Передача данных через порт PORTB (Часть 2)
|
Дмитрий Иванов, 09 Декабря 2013
|
|
Файлы к статье скачать
|
Имя: KA031.zip (ZIP архив) |
Размер: 50 КБ |
|
После прочтения прошлой статьи у Вас возможно возникли некоторые вопросы и сомнения - ну вот мы отправили один раз пакет из 1024 байт, а что дальше? Мне то нужно постоянно посылать в модуль данные, да причем не в консольном приложении а под Windows. Как это сделать? Как написать программу, чтобы она могла не только обслуживать операции записи данных но и парралельно могла выполнить другие функции?
Сейчас мы все эти вопросы и разрешим. Давайте сделаем следующее: напишем программу под Windows, которая будет "постоянно" отправлять данные в модуль WoodmanUSB. Слово "постоянно" дано в кавычках, т.к. разумеется, добавим возможность остановки записи и ее возобновления. За основу предлагаю вязть приложение под Windows из прошлых статей. Она уже имеет ряд готовых решений (открытие устройства, работа спортом PORTA) и нам осталось только вставить туда обраотку записи данных в порт PORTB.
Обратим внимание на внешний вид программы. Он претерпел некоторые изменения. Конкрентно, появились две кнопки, соотвественно для запуска записи данных и остановки. Также, с помощью статической строки текста на окне будем выводить число записанных байт данных.

Сперва залезаем в файл TestWinDlg.h. В описании класса диалога добалены две новые переменные: Terminate и ByteCounter. Первая будет определять, разрешена ли сейчас запись данных в порт PORTB (вообще, давайте договоримся, что если особо не указанно, то идет речь о записи данных через PORTB) или нет, вторая - простой счетчик отправленный данных в порт. Также есть еще две новые функции - они предназначены для обслуживания счетчика данных (его приращения и сброса).
......
class CTestWinDlg : public CDialog
{
// Construction
public:
CTestWinDlg(CWnd* pParent = NULL); // standard constructor
bool Terminate;
unsigned int ByteCounter;
void UpdateByteCounter(unsigned int WriteSize);
void ResetByteCounter();
// Dialog Data
.....
Перебираемся в файл TestWinDlg.cpp и переходим к обработчику нажатия кнопки Write. В первом приближении, код не очень то и большой, однако он содержит принципиально важный момент. Давайте по порядку. Сначала, устанавливаем перемнную Terminate в false, что будет означать что передача данных разрешена. Затем сбрасываем счетчик отправленных байт данных (код функции для сброса посмотрите сами, ибо он мягко скажем не замысловат). А теперь самое интересное. Мы запускаем программный поток с помощью MFC функции AfxBeginThread(). Для любителей Win API - внутри себя эта функция вызывает обычную апишную CreateThread(). Новый поток будет выполняться в рамках нашей собственной функции WriteRoutine(), адрес на которую мы и передаем в функцию создания нового потока. Туда же в каечтве дополнительно параметра, который будет передан в потоковую функцию передаем адрес на диалог (ключевое слово this). Зачем это нужно? Дело в том, что потоковая функция не будет являться членом класса диалога, и чтобы она смогла получить доступ к его переменныи и функциям мы передадим ей его адрес. А по адресу экземпляра класса уже можно все что угодно сделать.
void CTestWinDlg::OnWritePORTB()
{
// TODO: Add your control notification handler code here
Terminate = false;
ResetByteCounter();
AfxBeginThread(WriteRoutine, this);
}
Потоковая функция описана в верху текущего файла. Она имеет следующий вид:
UINT WriteRoutine(void* param);
А теперь давайте посмотрим собственно на саму реализацию этой функции. Сначала восстанавливаем адрес на диалог приобразуя void указатель к указателю на класс CTestWinDlg для получения доступа к его методам и функциям. Затем с помощью функции WUSB_SetupPortB() устанавливаем асинхронный режим работы порта PORTB. Далее для тестовых целей создаем массив данных для отправки в модуль. Затем запускаем "бесконечный" цикл, в котором отправляем данные в модуль. Цикл завершится в том случае, если мы установим флаг Terminate в состояние false, при записи данных произойдет ошибка или возникнет событие тайм-аута. Пока одно из этих событий не произойдет в модуль будут постоянно посылаться данные (при условии что контроллер PIC16F877 их считывает из модуля, а то выйдем по тайм-ауту). После каждой успешной записи переменная dwWrite будет содержать число успешно записанных байт данных. В конце каждого этапа цикла вызываем функцию UpdateByteCounter() нашего диалога (именно для этого и восстанавливали адрес на него), которая увеличит счетчик байт на величину dwWrite.
UINT WriteRoutine(void* param)
{
CTestWinDlg* dlg = (CTestWinDlg*)param;
WUSB_SetupPortB(ASYNC_MODE);
char buf[256];
for(int i = 0; i < sizeof(buf); i++)
{
buf[i] = i;
}
unsigned int dwWrite;
int status;
while(!dlg->Terminate)
{
status = WUSB_WritePortB(buf, sizeof(buf), &dwWrite);
if(status == WUSB_ERROR)
{
MessageBox(NULL, "Write Error!", "", MB_ICONERROR);
break;
}
else if(status == WUSB_TIMEOUT)
{
MessageBox(NULL, "TIMEOUT", "", MB_ICONERROR);
break;
}
dlg->UpdateByteCounter(dwWrite);
}
return 0;
}
Приведу код функций для работы со счетчиком записанных байт. Как я уже упомянул, функции эти не замысловатые и дополнительных комментариев на мой взгляд не заслуживают.
void CTestWinDlg::UpdateByteCounter(unsigned int WriteSize)
{
ByteCounter += WriteSize;
}
void CTestWinDlg::ResetByteCounter()
{
ByteCounter = 0;
m_senddata = "0";
UpdateData(false);
}
Ну и наконец, осталось рассмотреть код обработчика кнопки остановки записи. Здесь в первую очередь устанавливаем флаг Terminate (теперь в потоковой функции цикл завершится и функция окончит свою работу). Затем просто выводим на форму то что насчитал счетчик записанных данных.
void CTestWinDlg::OnStop()
{
// TODO: Add your control notification handler code here
Terminate = true;
m_senddata.Format("%d", ByteCounter);
UpdateData(false);
}
Итак, с софтварной частью разобрались. Хадварную пока оставляем без изменений с прошлой стаьи. Пора тестировать систему. Запускаем программу, открываем устройство и нажимаем кнопку Write. По еле заметному мерцанию светодиодов, подключенных к порту D микроконтроллера, можно понять, что данные пошли и контроллер их принимает. Например, давайте подождем секунд 10, а затем нажмем кнопку Stop. Передача данных прекратится, и на окне будет выведено суммарное число записанных байт данных за эти 10 сек. У меня получилось около 2.86 МБ (напомню, что в одном МБ содержится 1048576 байт). Т.е. около 0.29 МБ/с. Ну вообщем то как я и предупреждал в начале прошлой статьи - PIC16F877 просто больше считать в секунду не может.

Давайте запустим запись еще раз. Обратите внимание что параллельно с записью данных через порт PORTB модуля мы можем также и общаться с портом PORTA. Попробуйте установить маску ввода-вывода в виде числа 255 и зажигать/тушить светодиоды, подключенные к порту PORTA. Все работает.
Опять же у когото мог возникнуть вопрос - а зачем мы так "усложнили" программу, ввели в нее какие-то потоки? Что такое поток вообще и т.д. Объясняю. Начну с понятия потока. Мы находимся в ОС Windows - среда многозадачная. В один момент времени в этой ОС работают несколько программ "одновременно", в отличие от DOS, где выполняется только одна программа (если не считать резидентов в памати). Как Windows это делает, ведь процессор то у нее один? Все очень просто - ОС дает по очереди каждому запущенному приложению часть процессорного времени. И все. Т.е. реально в один момент времени в Windows тоже работает только одно приложение, все остальные не активны. Просто переключения между ними происходят очень быстро и мы этого вообщем то не замечаем. Теперь возвращаемся к нашему приложению. Каждое запущенно приложение есть процесс. В рамках процесса работает основной программный поток. В нашем случае это все обработчики нажатия кнопок, обработчики всех действий связанных с окном приложения (перемещение, закрытие, нажатие кнопкой мыши и т.д.). Т.е. по сути все что делает обычная программа - она делает в рамках основного программного потока. Однако есть возможность создать еще несколько потоков со своим программным кодом. Теперь когда Windows передаст управление нашей программе (состоящей из двух потоков) а все остальные программы заморозит она сначала даст немного времени поработать основному потоку (и мы сможем например кнопки нажимать), затем даст немного времени следующему потоку (данные в порт PORTB будем записывать), затем следующему и т.д. Теперь, предположим что мы разместили бы код записи данных в порт PORTB в основном рабочем потоке, например, просто в коде обработчика нажатия кнопки на форме. Что бы произошло? Да ни чего хорошого - запустится бесконечный цикл, который будет "жрать" все процессорное время для нашего приложения, а остальные операции программа выполнять бы не смогла. Напрмер, если бы мы попробовали переместить окно, или понажимать другие кнопки - программа не отреагировала бы на это и вконце концов зависла бы.
Хорошо, с потоками более мение разобрались, но кто-то может задаться вопросом - вот мы отправляем данные в контроллер, он их там считывает, показывает на светодиодах, но я то ведь их не вижу, а вдруг модуль ошибается при передаче и я считываю в контроллере совсем не то что я посылаю? Как мне в тестовых целях оттрасировать поток пересылаемых данных?
Могу предложить следующий вариант - давайте данные которые примет контроллер отсылать через COM порт обратно на компьютер. Так мы сможем точно проверить все ли данные были отправлены и убедится в отсутствии искажений при передаче. Для реализации этой идеи нам придется немного изменить хардварную и фирмварную части нашего проекта. Займемся "железом". Необходимо добавить возможность работы с COM портом. Для этого потребуется хорошо нам знакомый преобразователь уровней TTL-RS232 микросхема MAX232. К схеме из предыдущей статьи добавим вот такую "приставочку". Показанный разъем на схеме соответсвует разъему "мама" COM порта, т.е. его нужно вставлять в COM порт торчащий из системного блока ПК (там стоит "папа").

Я собрал такую приставочку в виде простенькой платы для удобства использования. Питание для MAX232, разумеется берем от USB как и для 74LS244.


Тперь займемся фирмварной частью - а именно прошивкой контроллера, т.к. нам необходимо добавить возможность работы с COM портом. Итоговый код показан ниже:
#include <pic.h>
__CONFIG(0x03F72);
unsigned char temp;
void Send(unsigned char datatus)
{
if((TXEN) && (TRMT))
{
TXREG = datatus;
while(!TRMT);
}
}
void USART_Init()
{
SPBRG = 31; //31 - 9600; 20 - 57600
TXEN = 1;
CREN = 1;
SPEN = 1;
SYNC = 0;
RCIF = 0;
}
void main(void)
{
USART_Init();
//**************** Шины данных ********************
TRISB = 255; // настраиваем все линии порта B на вход
PORTB = 0;
TRISD = 0; // настраиваем все линии порта D на выход
PORTD = 0;
//******* Линии контроля и управления *************
/*
вывод С0 будем использовать как сигнал чтения
(вывод PB_RD модуля);
настраиваем его на выход
*/
TRISC0 = 0; // PB_RD
/*
вывод С2 будем использовать для слежения за состоянием
IN_FIFO буфера (вывод PORTB_FNE модуля);
настраиваем его на вход
*/
TRISC2 = 1; // PORTB_FNE
RC0 = 0;
RC2 = 0;
//**********************************************
while(1 == 1)
{
if(RC2 == 1) //в IN_FIFO буфере модуля есть данные
{
RC0=!RC0;
if(RC0 == 0)
{
temp = PORTB;
PORTD = temp;
RC0 = 1;
Send(temp);
}
}
else
{
Send('N');
Send('O');
}
}
//**********************************************
}
Что здесь нового? Функция USART_Init(), которая проводит начальную инициализацию последовательно порта и настройку его работы на скорости 9600 Байт/c. Теперь когда мы считываем каждый новый байт из WoodmanUSB мы помимо его индикации на светодиодах отправляем его через COM порт обратно на компьютер с помощью функции Send(). Если данных нет, отсылаем два символа - 'NO'.
Зашиваем прошивку в контроллер. Подключаем схему к USB. Теперь давайте ришим следующий вопрос - как мы будем получать данные по COM порту. Ведь их у нас не несколько байт а целая куча (ограничивается только кнопкой Stop). Предлагаю данные которые сыпятся из COM порта писать в файл, а затем его уже анализировать. В качестве программы, обладающей такой функциональностью предлагаю использовать стандартную терминальную программу, входящую в состав Windows - HyperTerminal.
Запускаем Пуск -> Программы -> Стандартные -> Связь -> HyperTerminal. Если Вы делаете это впервый раз, Вас могут спросить о желании сделать эту программу TelNet приложением по умолчанию. Согласитесь. Далее нужно будет как то обозвать создаваемое соединение, например Test.

Выбираем COM порт, к которому подключена наша тестовая схема. У меня это COM2.

Задаем параметры работы порта. Обязательно поставьте скорость 9600 и управление потоком - НЕТ. Нажимаем Применить, затем Ok.

Появится окно в котором будут печататься принимаемые символы из порта. Если схема сейчас подключена, то мы должны увидеть кучу повторяющихся NO. Действительно, данных в IN_FIFO модуля нет и контроллер шлет через COM соответствующее сообщение. Если вместо NO Вы видете какую-то чушь, попробуйте перезагрузить схему - последовательный порт МК глючит.

Давайте включим протоколирование данных. Для этого в меню HyperTerminal`a выбираем Передача -> Запись протокола в файл.... Указываем путь к файлу в который будут помещаться прочтенные данные.

Теперь запускаем нашу программу для записи данных в порт PORTB модуля WoodmanUSB. Открываем устройство, нажимаем кнопку Write. Программа начнет пересылать данные, контроллер их принимать и отправлять обратно на ПК, где HyperTerminal их печатает и парралельно пишет в файл. Рекомендую отключить динамики, да Вы и сами наверное это уже сделали. Дело в том, что в потоке данных HyperTerminal находит некий управляющий символ (\r\n или в HEX 0x0D, 0x0A) по которому он дает зыуковой сигнал и переводит редактор на новую строку. Нам это не мешает, только если звук, но с ним мы уже поборолись.
Мы должны увидеть нечто вроде такого - набор друг за другом идущих символов. Если вспомнить код для отправки данных, то все становится понятно. Мы ведь по сути дела отправляем весь набор однобайтных символов от 0 до 255. Часть их них является печатными, другая нет. Те коды которым нет соответствующих печатных символов HyperTernimal рисует в виде "псевдо-графики". Но нас это все мало интересует - смотреть данные мы будет всеравно из файла в HEX-редакторе.

Попишите немного, затем остановите запись данных в нашей программе, а затем и запись протокола в терминале. Не удивляйтесь увиденному числу записанных байт данных - чего Вы хотели, передача по последовательному порту да и еще на 9600 это не быстрая операция. PIC16F877 не может быстро считывать данные, соответственно и суммарная скорость системы получилась небольшой.

Пора смотреть что мы там понаписали. Смотреть данные будем в HEX виде. Сейчас существует целая куча HEX редакторов под Windows. Я по привычке буду использовать старый добрый Far. Открываем файл и переводим его в HEX вид нажатием клавиши F4.

Ну вот и то что мы хотели увидеть. В левой колонке данные представлены в HEX кодах, в правой - в печатном виде по возможности. Обратите внимание на красную метку - с этого моента начала работу наша программа. До этого данныъх не было и контроллер слал NO. Коды символов возрастают на еденицу и на желтой метке достигает максимально значения - 0xFF. Все, один пакет из 256 байт данных был передан (отработал один проход цикла while() в нашей программе). Затем все повторяется, т.к. в нашей тестовой программе мы содержимое буфера с данными от записи к записи не меняли.

Надеюсь, у Вас все получилось и Вы смогли убедится в адекватности работы системы WoodmanUSB + PIC16F877 (или с другим контроллером). На данный момент мы разобрали принципы работы с WoodmanUSB в режиме передачи данных из компьютера во внешнее устройство. Мы не получали больших скоростей передачи, т.к. пояснить всю эту кухню мне было проще на более простом приборе в виде PIC контроллера чем сразу переходить на ПЛИС или микропроцессор. В конечном итоге сишный код для ПИКА будет не сильно отличаться для любого другого прибора, будь то AVR или ARM.
© Дмитрий Иванов
09 Декабря 2013 года
http://www.kernelchip.ru