Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 31. WoodmanUSB. Передача данных через порт PORTB (Часть 2)

Дмитрий Иванов, 09 Декабря 2013

Файлы к статье скачать
Имя: KA031.zip (ZIP архив)
Размер: 50 КБ

После прочтения прошлой статьи у Вас возможно возникли некоторые вопросы и сомнения - ну вот мы отправили один раз пакет из 1024 байт, а что дальше? Мне то нужно постоянно посылать в модуль данные, да причем не в консольном приложении а под Windows. Как это сделать? Как написать программу, чтобы она могла не только обслуживать операции записи данных но и парралельно могла выполнить другие функции?

Сейчас мы все эти вопросы и разрешим. Давайте сделаем следующее: напишем программу под Windows, которая будет "постоянно" отправлять данные в модуль WoodmanUSB. Слово "постоянно" дано в кавычках, т.к. разумеется, добавим возможность остановки записи и ее возобновления. За основу предлагаю вязть приложение под Windows из прошлых статей. Она уже имеет ряд готовых решений (открытие устройства, работа спортом PORTA) и нам осталось только вставить туда обраотку записи данных в порт PORTB.

Обратим внимание на внешний вид программы. Он претерпел некоторые изменения. Конкрентно, появились две кнопки, соотвественно для запуска записи данных и остановки. Также, с помощью статической строки текста на окне будем выводить число записанных байт данных.

программирование USB

Сперва залезаем в файл 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 просто больше считать в секунду не может.

программирование USB

Давайте запустим запись еще раз. Обратите внимание что параллельно с записью данных через порт PORTB модуля мы можем также и общаться с портом PORTA. Попробуйте установить маску ввода-вывода в виде числа 255 и зажигать/тушить светодиоды, подключенные к порту PORTA. Все работает.


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



Хорошо, с потоками более мение разобрались, но кто-то может задаться вопросом - вот мы отправляем данные в контроллер, он их там считывает, показывает на светодиодах, но я то ведь их не вижу, а вдруг модуль ошибается при передаче и я считываю в контроллере совсем не то что я посылаю? Как мне в тестовых целях оттрасировать поток пересылаемых данных?

Могу предложить следующий вариант - давайте данные которые примет контроллер отсылать через COM порт обратно на компьютер. Так мы сможем точно проверить все ли данные были отправлены и убедится в отсутствии искажений при передаче. Для реализации этой идеи нам придется немного изменить хардварную и фирмварную части нашего проекта. Займемся "железом". Необходимо добавить возможность работы с COM портом. Для этого потребуется хорошо нам знакомый преобразователь уровней TTL-RS232 микросхема MAX232. К схеме из предыдущей статьи добавим вот такую "приставочку". Показанный разъем на схеме соответсвует разъему "мама" COM порта, т.е. его нужно вставлять в COM порт торчащий из системного блока ПК (там стоит "папа").

программирование USB

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

программирование WoodmanUSB

программирование WoodmanUSB

Тперь займемся фирмварной частью - а именно прошивкой контроллера, т.к. нам необходимо добавить возможность работы с 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.

программирование USB HighSpeed

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

программирование USB HighSpeed

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

программирование USB HighSpeed

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

программирование USB HighSpeed

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

программирование USB HighSpeed

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

Мы должны увидеть нечто вроде такого - набор друг за другом идущих символов. Если вспомнить код для отправки данных, то все становится понятно. Мы ведь по сути дела отправляем весь набор однобайтных символов от 0 до 255. Часть их них является печатными, другая нет. Те коды которым нет соответствующих печатных символов HyperTernimal рисует в виде "псевдо-графики". Но нас это все мало интересует - смотреть данные мы будет всеравно из файла в HEX-редакторе.

программирование USB HighSpeed

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

программирование USB HighSpeed

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

программирование USB HighSpeed

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

программирование USB HighSpeed



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


© Дмитрий Иванов
09 Декабря 2013 года
http://www.kernelchip.ru



© KERNELCHIP 2006 - 2023