Страница 1 из 2
Прием и обработка строк в CVAVR
Добавлено: Вт июн 23, 2009 14:54:22
kupriyanov
Уважаемые коллеги!!!
Уже который месяц не могу справится с проблемой надежного приема строк по USART с телефона в контроллер для обработки. Все дело в том, что существующие для этого средства CVAVR мягко говоря не вполне комфортно приенять. А именно пытаюсь приручить функцию scanf(). Она работает достаточно нестабильно, контроллер зависает, если функция вызывается без наличия строк в буффере.
даже если проверять наличия символов в буффере напрмер так:
где rx_counter - это счетчик символов в буффере в генерируемом коде CVAVR
Все равно программа виснет в этом месте.
Я подозреваю что нужно реализовать проверку ни сколько наличия символов в буффере, сколько непрочтенного символа конца строки.
Может есть у кого какие наработки по этому вопросу
Добавлено: Вт июн 23, 2009 15:08:25
ARV
я буквально 10 минут назад закончил свои ковыряния со строками... но в WinAVR. принципиально это дела не меняет, НО! я использую синхронный ввод. т.е. без применения циклических буферов приема и обработки прерываний. тупо жду. пока придет символ в UDR.
анализ библиотеки avr-libc из комплекта WinAVR показал, что наиболее надежно работать с fgets(), так как она позволяет контролировать переполнение буфера приема строки. однако, я сделал иначе.
если интересует алгоритм - могу рассказать. в принципе, и кодом могу поделиться, если не пугает то, что он для WinAVR

Добавлено: Вт июн 23, 2009 15:09:50
DrWatson
Думаю в данном случае лучше считывать принятые байты по одному в отдельный буфер, а после прочтения символа конца строки уже использовать пресловутый scanf(), для верности можно в этом буфере заменить символ конца строки нулем или ноль добавить после него.
Примерно так.
Код: Выделить всё
if(rx_counter)
{
MyBuffer[i]=getchar();
if((MyBuffer[i]==0x0d)&&(i>0))
{
MyBuffer[i]=0;
scanf(MyBuffer, ....);
i=0;
}
}
else i++;
Добавлено: Вт июн 23, 2009 15:15:10
ARV
DrWatson писал(а):Примерно так.
да, примерно так

только в данном случае получается двойная буфферизация - CVAVR создает циклический буфер приема, а в дополнение вы из этого буфера себе в буфер все копируете. расточительно по ОЗУ
Добавлено: Вт июн 23, 2009 15:24:53
DrWatson
Про ОЗУ - согласен. Но переделывать код Мастера - себе дороже. Хотя в нем можно задать буфер больше, чем максимальная длина пакета данных и попытаться работать напрямую с буфером.
ИМХО поудалять нафиг все, что Мастер написал кроме заголовков обработчиков прерываний. И переписать все по-своему.
Добавлено: Вт июн 23, 2009 16:30:11
kupriyanov
ARV
Будте добры и вы кодом поделитесь!!!
Думаю не будет принципиальной разницы в ЦВАВР и ВинАВР, ведь не код собираюсь копировать, а принцып понять так сказать на примере
Добавлено: Вт июн 23, 2009 16:40:38
kupriyanov
2
DrWatson
Спасибо за хороший пример, чуть чуть его поправлю можно???
Там необходимо использовать функцию sscanf();
Хотя... блин, может и я туплю выто про WinAVR....
в CVAVR функция sscanf читает из входной строки а scanf() только с усарта используя getchar();
Добавлено: Вт июн 23, 2009 16:53:31
DrWatson
Да, действительно sscanf();
просто я ими не пользовался, потому и ошибся

Добавлено: Вт июн 23, 2009 16:56:12
kupriyanov
А в WinAVR больше возможностей по сравнению с ЦВАВР??
Может уже стоит переходить от простоты использования к эффективности

Добавлено: Вт июн 23, 2009 18:31:14
kupriyanov
И еще вопросец!!!
Еще телефон передает после символа 0x0D символ 0x0A, может его вообще просто игнорировать...
Предположим при запросе AT, телефон ответит OK(в терминале).
Реально же с него идет "0D 0A 4F 4B 0D 0A"
Вот на этот набор символов ваша программа тоже среагирует не совсем четко. Первый OD она проигнорирует, а вот OA запишет себе - это мне не желательно.
Как вы считаете?? может стоит 0A игнорировать???
И кстати.... уточню. Ваш код DrWatson должен быть циклом дополнен правильно??? и строка else i++; должна относиться к if((MyBuf.... или я не прав... в данном виде он работать не будет.. нет цикличности, не ставить же его в прерывание по приему символа.
Добавлено: Вт июн 23, 2009 20:33:33
ARV
в
WinAVR нет мастера кода, поэтому многое там надо делать ручками. зато лучше понимаешь, что к чему.
по поводу символов завершения строки. символ
'\n' надо просто игнорировать, а символ
'\r' заменять на 0, т.е. обозначать им конец строки.
scanf делает это автоматически, если надо - делайте это вручную
я в своем случае сделал очень просто: завел массив строк-команд во FLASH, потом читаю из потока строку с ограничением длины, потом при помощи библиотечной функции
strtok() парсю строку по словам (первое слово - команда, остальные - аргументы команды), затем обычным циклом перебираю массив строк и сравниваю с первым словом в принятой строке. как совпало - значит, я знаю, какая команда принята. это вкратце, код завтра покажу.
Добавлено: Вт июн 23, 2009 20:37:01
DrWatson
1. мой кусок кода конечно же предполагался в теле цикла, я while()... ставить не стал, мало ли что там еще в этом цикле должно делаться по ходу программы, а начальная инициализация переменной i перед циклом.
2. Да действительно, с else Вы правы. Совсем я невнимательным стал - старею
3. В принципе можно сделать проверку и на то и на другое, а лишнее отбрасывать:
Код: Выделить всё
i=0;
while(1)
{
if(rx_counter)
{
MyBuffer[i]=getchar();
if(((MyBuffer[i]==0x0d)||(MyBuffer[i]==0x0a))
{
if(i>0)
{
MyBuffer[i]=0;
sscanf(MyBuffer, ....);
i=0;
}
}
else i++;
}
// Здесь еще возможно какие-то действия
}
В этом случае "лишние" символы будут пропускаться, т.е. независимо от того придет ли в конце строки CR или LF или оба в той или иной последовательности - результат должен быть одинаков.
Добавлено: Ср июн 24, 2009 07:16:29
ARV
вот куски обещанного мной кода.
Команды у меня имеют такой формат:
CMD [
аргументы].
1. сделаны такие объявления
Код: Выделить всё
/// тип: символ в памяти программ
typedef PROGMEM char pgmchar;
/// тип-перечисление: варианты команд
typedef enum _command_t {
cmd_dig_out /// управление релейными выходами
,cmd_dig_in /// ввод состояний дискретных линий
,cmd_extend /// зарезервированная команда расширенного управления
,cmd_ana_in /// получение значений АЦП по входам
,cmd_info /// информация о программе
,cmd_status /// текущее состояние
,cmd_help /// справка по командам
,cmd_reset /// программный "горячий" сброс
,cmd_last // ПОСЛЕДНЯЯ команда - не обрабатывается, просто счетчик
} command_t;
// строки в сегменте памяти программ
pgmchar dout[] = "DOUT";
pgmchar din[] = "DIN";
pgmchar ext[] = "EXT";
pgmchar ain[] = "AIN";
pgmchar info[] = "INFO";
pgmchar status[]= "STATUS";
pgmchar hlp[] = "HELP";
pgmchar rst[] = "RESET";
pgmchar s_ok[] = "OK";
pgmchar s_err[] = "ERROR";
/// массив строк-команд
PGM_P cmd_str[cmd_last] = {
// порядок строк должен совпадать с порядком констант cmd_xxxx
dout, din, ext, ain, info, status, hlp, rst
};
2. Функция ввода строки.
Код: Выделить всё
/** Ожидание и ввод строки-команды
* Функция ждет, пока будет введена строка. Возвращает управление только в том
* случае, если получен символ "конец строки".
* @param str - буфер для приема строки
* @param len - максимальное число символов в команде
* \note Все введеные символы сверх #len игнорируются
*/
static void getstring(char* str, uint8_t len){
char ch, ch1;
do{
ch1 = getchar(); // получаем символ из потока ввода
if(ch1 == '\n') continue; // если это перевод строки - игнорируем
if((ch1 == '\r') // если это возврат каретки
|| (len == 0)) // или сверхлимитные символы,
ch = 0; // то это значит конец ввода,
else
ch = ch1; // иначе символ надо сохранить
if(len){ // только если лимит не исчерпан,
*str++ = ch; // сохраняем введенный символ
len--; // и ведем счет символов
}
} while (ch1 != '\r'); // если в потоке конец строки - выходим
}
Обратите внимание, что функция принимает символы в любом количестве (до нажатия ENTER), но реально возвращает не более заданного количества! это было нужно мне, но может быть лишним для вас.
3. функция приема команды
Код: Выделить всё
/** Возвращает код принятой команды
* Функция вводит строку-команду из потока ввода, ищет совпадения в массиве
* поддерживаемых команд cmd_str и, если находит, возвращает тип команды.
* Если введена неподдерживаемая команда, функция выводит в поток вывода
* сообщение об ошибке ввода и не возвращает управление.
* @param arg - указатель на строку "остатка" команды, т.е. части команды, содержащей параметры
* @return - тип принятой команды
*/
static command_t get_cmd(char **arg){
static char tmp[10];
char *cmd;
do{
getstring(tmp,10); // прием строки-команды
strupr(tmp); // перевод ее к верхнему регистру
cmd = strtok(tmp," "); // выделение первой части команды
for(uint8_t i = 0; i < cmd_last; i++){ // поиск первой части в массиве
if(strcmp_P(cmd, cmd_str[i])==0){ // если найдено совпадение,
*arg = strtok(NULL," "); // то получаем строку параметров
if(strtok(NULL," ")){ // если параметров больше одного,
break; // то это ошибка ввода
}
else return i; // а иначе - возвращаем результат
}
}
if(tmp[0]) result(ER); // непустая ненайденная строка - ошибка
} while(1); // крутимся в цикле вечно
}
обратите внимание, что обе функции не возвращают управление, пока не примут подходящую команду! это было нужно мне, но может быть ненужным вам.
4. вот так я обрабатываю полученные команды в главном цикле
Код: Выделить всё
char *arguments; // строка аргументов команды
cmd = get_cmd(&arguments);
switch(cmd){
//--------------------------------------------------------------------------
case cmd_dig_out:// управление релейными выходами
number = strtoul(arguments,NULL,0); // получаем маску вывода
if(errno == ERANGE){ // если параметр - не число,
result(ER); // то сигнализируем об ошбике
break; // и все.
}
главное в этом кусочке, на что я хотел обратить ваше внимание, это обработка "хвоста" команды, т.е. списка аргументов.
Добавлено: Ср июн 24, 2009 07:43:48
ikarab
Спасибо. Все четко прокомментировано. Красиво ИМХО.
Добавлено: Ср июн 24, 2009 08:05:29
kupriyanov
Да.. все круто разложено... Спасиб

Добавлено: Ср июн 24, 2009 08:19:46
ikarab
Еще бы проектик ! Чтоб чик-чик и посимулировать ...
Добавлено: Ср июн 24, 2009 08:57:09
ARV
ikarab писал(а):Еще бы проектик ! Чтоб чик-чик и посимулировать ...
ну чикайтесь, ежели хочется

симулянты...

не забудьте в настройках терминала протеуса эхо включить...
Добавлено: Ср июн 24, 2009 10:14:56
ikarab
Спасибо ! Когда видишь как все это крутится-вертится - повеселей всеж ...
Добавлено: Сб июн 27, 2009 15:05:10
kupriyanov
Уважаемый
ARV
А в вашем случае прога не висит в месте:
в случае, если на вход ничего не поступало????
Например в CVAVR в реализации функции getchar() есть строка:
Нужно полагать перед тем как обратиться к этой функции нужно убедиться, что что-нить на вход пришло???? или в WinAVR эта функция написана по-другому??
Добавлено: Сб июн 27, 2009 15:34:13
kupriyanov
Кстати в функции sscanf() необходимость полностью отпадает.