Неблокируемый класс HardwareSerial

Неблокируемый класс HardwareSerial.

Собственно все началось с того, что мне нужно было проверять датчики, подключенные к arduino, даже тогда, когда я вывожу информацию в терминал.
Проверяя длину одного цикла loop() обнаружил, что при выводе информации в порт, время цикла резко возрастает.
Начал выяснять, и обнаружил, что класс HardwareSerial при выводе данных в COM порт переходит в состояние ожидания опустошения буфера обмена.
Вот этот код
// If the output buffer is full, there's nothing for it other than to 
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail)
;

Это все хорошо для разработки простых однопоточных программ (собственно Arduino первоначально для этого и создавалась). Но уж очень хочется использовать все прелести микроконтроллера и его прерываний. Для этого была, например, разработана библиотека TimerOne.

Так вот…
Пришлось немного покопаться во внутренностях библиотеки, которая идет в комплекте со средой Arduino.
Поскольку раньше никогда с AVR дела не имел, пришлось пару дней поискать информацию по внутренностям микроконтроллера и работой с прерываниями.
Все оказалось достаточно просто.
Для совместимости ввел признак Serial.blocked (по умолчанию имеет значение true) так что все программы написанные ранее должны работать без изменений в коде.
Если вы хотите использовать неблокируемую передачу, то вы должны явно перейти в неблокируемый режим Serial.blocked = false;

Теперь об изменениях…
Изменения коснулись нескольких функций.

Функция begin
void HardwareSerial::begin(unsigned long baud)
{
  uint16_t baud_setting;
  bool use_u2x = true;
  blocked = true; // Set blocked tag for compatibility

Добавил инициализацию признака blocked для совместимости.

Функция store_char
inline int store_char(unsigned char c, ring_buffer *buffer)
{
	int i = (unsigned int)(buffer->head + 1) % SERIAL_BUFFER_SIZE;

	// if we should be storing the received character into the location
	// just before the tail (meaning that the head would advance to the
	// current location of the tail), we're about to overflow the buffer
	// and so we don't write the character or advance the head.
	if (i != buffer->tail) {
		buffer->buffer[buffer->head] = c;
		buffer->head = i;
		return 1;
	}
	else{
		return 0;
	}
}

Теперь функция возвращает 1 при записи символа в порт, и 0 если буфер заполнен.

Новая функция tx_available, возвращает количество свободных байт в буфере передачи.
int HardwareSerial::tx_available(void)
{
  return (unsigned int)(SERIAL_BUFFER_SIZE - (SERIAL_BUFFER_SIZE + _tx_buffer->head - _tx_buffer->tail) % SERIAL_BUFFER_SIZE);
}


И главное изменение в функции write
size_t HardwareSerial::write(uint8_t c)
{
	if(blocked){
		int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
		
		// If the output buffer is full, there's nothing for it other than to 
		// wait for the interrupt handler to empty it a bit
		// ???: return 0 here instead?
		while (i == _tx_buffer->tail)
			;
	
		_tx_buffer->buffer[_tx_buffer->head] = c;
		_tx_buffer->head = i;
		
		sbi(*_ucsrb, _udrie);
		return 1;
	}
	else{
		if (tx_buffer.head == tx_buffer.tail && ( UCSR0A & (1<<UDRE0)) != 0){
			// If Tx buffer empty and Tx interrupt disabled, so send char
			// directly to register and enable Tx interrupt

			#if defined(UDR0)
				UDR0 = c;
			#elif defined(UDR)
				UDR = c;
			#else
				#error UDR not defined
			#endif
			sbi(*_ucsrb, _udrie);
			return 1;
		}
		return store_char(c, _tx_buffer);
	}
}

Собственно всё…

При использовании неблокируемого вывода программист сам должен позаботиться о том, чтобы все данные попали в буфер передачи.
Перед выводом данных необходимо проверить, есть ли место в буфере передачи, иначе более новые данные перезапишут еще не переданную из буфера информацию.
Режим вывода можно переключать в любое время (Serial.blocked = true/false;)

Пример кода
#include <arduino.h>

unsigned long loop_counter;

void setup() {
	// initialize serial:
	Serial.begin(9600);
	//Serial.blocked = true;					// Blocked operation for Arduino compatibility mode. This tag set to true as default value
	//Serial.blocked = false;					// Uncomment this line for new nonblocked operations. 
}

void loop() {
	bool send_tag;
	loop_counter++;
	if(Serial.available()>0){
		if(Serial.read() == ' '){
			digitalWrite(13,1);
			Serial.blocked = false;
		}
		else{
			digitalWrite(13,0);
			Serial.blocked = true;
		}
	}

	if(Serial.blocked == false)
		send_tag = Serial.tx_available() > 60;
	else
		send_tag = true;
	
	if(send_tag){
		Serial.println("1234567890");
		Serial.println("1234567890");
		Serial.println("1234567890");
		Serial.println("1234567890");
		Serial.println(loop_counter);			// Look this counter in terminal for blocked and for nonblocked operations
		loop_counter = 0;
	}
}

Возможно, продолжение следует…

Google Code
code.google.com/p/nb-hardware-serial/

Ссылки
www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
easyelectronics.ru/avr-uchebnyj-kurs-peredacha-dannyx-cherez-uart.html
www.multiwii.com

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

RSS свернуть / развернуть
+
+2
Спасибо! Немногие решаются лезть внутрь «кубиков», из которых строится мир :)
avatar

able

  • 19 марта 2012, 13:42
+
0
Когда полез в код, сначала тоже было страшновато.
Не хотелось запороть единственный контроллер.
После 2 дней копания понял, что не все так страшно.
Скорее всего код еще немного измениться, поэтому «Возможно продолжение...» :)
Но этот код работоспособен.
Только архив с кодом не знаю куда лучше выложить. :(
avatar

GraninDm

  • 19 марта 2012, 13:50
+
+1
лучше всего исходники на гитхаб залить или гугл-код ;)
avatar

admin

  • 19 марта 2012, 18:12
+
0
Добавил ссылку на Google code.

А можно перенести статью в раздел «Коммуникации»?
А то я что-то не понял как это сделать.
Или мне пока нельзя туда писать?
avatar

GraninDm

  • 20 марта 2012, 06:06
+
0
на блоги есть ограничение по рейтингу.
Перенёс в Arduino.
avatar

admin

  • 20 марта 2012, 06:20
+
0
Спасибо! Тоже всегда боялася неведомого)
avatar

Luan

  • 19 марта 2012, 14:34

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.