Обсуждаем контроллеры компании Atmel.
Ответить

помогите с потоком AVR

Пн апр 21, 2014 14:35:19

Добрый день форумчане.Не могу разобраться с потоком.
Вот пример:
int
uart_putchar(char c, FILE *stream)
{

if (c == '\n')
uart_putchar('\r');
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;

ссылка на документацию : http://avr-libc.narod.ru/group__avr__stdio.html
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

Зачем второй параметр, FILE *stream -зачем нужен?.

Но без этого параметра компилсятор выдает предупреждение вот такого формата: Warning 1 initialization from incompatible pointer type
Почему так происходит?
Буду рад даже ссылки на подобную проблему.

Re: помогите с потоком AVR

Пн апр 21, 2014 16:23:15

http://www.nongnu.org/avr-libc/user-man ... 271b7227fb

Re: помогите с потоком AVR

Пн апр 21, 2014 18:07:35

не совсем понял, т.к документация на англ. но.
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE );
в документе приводится пример создания этого потока, т.е в скобках я указал uart_putchar - это функция которой будет вызываться на отправку символа, потом следует NULL т.к я не буду принимать символ , ну и параметр _FDEV_SETUP_WRITE говорит на то что я буду использовать поток только для отправки символа.
но зачем я вот этого не пойму при объявлении функ. надо писать FILE *stream???
uart_putchar(char c, FILE *stream)
{
тут функция что-то делает
return 0;
}
Кстати когда я объявляю функцию uart_putchar(char c, FILE *stream) таким способом то уже вызывать функцию я должен только printf -ефом, а вызвать ее без printf т.е так uart_putchar (simvol) , компилятор выдаст ошибку.

Re: помогите с потоком AVR

Вт апр 22, 2014 19:58:02

Ребята что не кто не сталкивался с потоком ??? .

Re: помогите с потоком AVR

Вт апр 22, 2014 21:56:35

сталкивались, но с IBM, а не AVR. Здесь как-то все немного по другому и на мой взгляд, потоки в АВР - чрезмерное усложнение, ведущее исключительно к увеличению ресурсозатрат. Лучше разберитесь со всем этим на уровне регистров.

Re: помогите с потоком AVR

Вт апр 22, 2014 22:40:24

mideni, использование потоков в таких небольших МК - большая экзотика, примерно как передавать аргументы в main() в тех же условиях.

Даже не могу сходу придумать, зачем такое могло бы понадобиться. Так что с этим похоже и правда мало кто сталкивался.

Re: помогите с потоком AVR

Вт апр 22, 2014 22:41:03

Да согласен что в IBM немножко по другому, и согласен что в avr использовать потоки затратно, но очень хотелось бы понять суть!
Зачем этот второй параметр? я про FILE *stream, я от этого не могу понять.
Как-то странно. Когда я передаю параметр в функцию то я его и ловлю,передаю два , и соответственно я этих два параметра ловлю
ну например
main()
{
char aa=1,bb=2;
function(aa,bb);
}
void function (char a, char b)
{
тут что-то делаем;
}

а с потоком мне не понято

Re: помогите с потоком AVR

Ср апр 23, 2014 00:41:38

Чисто логически можно предположить следующее. Простой C - это процедурный язык. Когда некоторый набор функций принадлежит какому-то общему "объекту", то раньше во всех функциях добавляли указатель на этот объект, чтобы в любой из них можно было получить данные об окружении текущей операции, тем более, когда предполагается последовательность операций.

Представьте себе мк с 10 USART и для каждого идёт работа при помощи prinntf(). Как привязать один printf() к конкретному порту? Вот, думается мне, для этого и есть указатель на некоторый объект. Нужно читать заголовочники, где все эти штуки описаны.

Итак, у нас 10 портов и мы хотим поочереди записать в каждый порт "Hello world!", как это можно сделать при помощи printf() и одного стандартного "потока"? На самом деле это структуры типа FILE.

Вариант 1. Использовать глобальную переменную, которая бы соотв-вала бы номеру текущего порта для вывода. Перед очередным использованием printf() менять эту переменную, а в put_char() следить за глобальной переменной.

Вариант 2. Использовать поле udata (user defined and accessible data) структуры stream, которая имеет тип FILE. Т.е., поскольку стандартный вывод у нас соотв-ет переменной stdout, то мы можем обратиться к полю stdout->udata, записать туда номер текущего порта для вывода, а потом проверять stream->udata внутри put_char(). Я не проверял, но, видимо, второй параметр будет инициализирован ссылкой на stdout.

Может быть я чуть запутанно поясняю, но смысл в том, чтобы иметь сквозной объект для хранения текущих параметров при выполнении ряда связанных операций. Сейчас в больших ЯВУ изменилась идеология и нет необходимости иметь ссылку на объект, т.к. сам объект содержит в своём описании всё, что нужно для работы его методов.

Т.е. "поток" используется, но это неявно. Поскольку функции универсальные, то этот параметр используется для универсальности.

П. С. Т.е. это this, только для процедурного языка. Если вы программировали на c#, vb.net или чем-то подобном, то знаете, что внутри метода можно обратиться к объекту-хозяину через указатель this. Так вот это он самый и есть, только для сущности, которая у нас тут называется FILE. И stream - это указатель на текущий экземпляр FILE.

Re: помогите с потоком AVR

Ср апр 23, 2014 15:58:49

да с несколькими UART согласен.
но есть еще один вариант правда экзотический с несколькими потоками, ну например. есть 2 uart.
первый uart будет стандартный поток второй будет созданий.
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); //для стандартного потока.
static FILE stdout_LCD_num2 = FDEV_SETUP_STREAM(uart_putchar_num2, NULL, _FDEV_SETUP_WRITE );
ну потом
main()
{
stdout=&stdout_LCD; // для того чтобы просто писать printf(); а не fprintf(stdout_LCD, и дальше что надо);
printf("HELLO"); //для первого usart
fprintf(uart_putchar_num2,"hello"); // для второго uart;
}
void uart_putchar_num2(char c, FILE *stream)
{
// тут драйвер-функция для второго uart.
// недостаток этого что для каждого uart надо писать свою функцию, что очень не желательно для мк. Но как вариант можно-ли да.
}
если использовать printf() для lcd 1602 нам надо инициализировать дисплей (можно использовать для иниц. один поток, для отправки текста
другой поток. Если честно по поводу udata надо разобрать более детально.(я ее не использовал ни когда)
----------
еще один пример :
main ()
{
int a=20;
int b=10;
my_func(b,&a);
}
void my_func (int temp, int *tmp)
{
// теперь переменная temp скопировалась, а переменная tmp ну я работаю с ней через адрес, и явного копирования не было.
// тут можно что угодно ну например.
*tmp=*tmp+temp;
}
но как видно я сделал так т.е. явно указал my_func(b,&a); а с потоком я не что такого не делал. как бы все авто.
Значить в каких-то вариантах надо делать , т.е явно указывать пример с &a(я указывал всегда), а в каких то вариантах не надо, почему ???

Re: помогите с потоком AVR

Ср апр 23, 2014 17:47:50

По поводу udata есть даже комментарии в заголовочном файле и макросы для использования этого поля, которые я раньше не заметил (stdio.h):
Код:
/** This macro inserts a pointer to user defined data into a FILE
    stream object.

    The user data can be useful for tracking state in the put and get
    functions supplied to the fdevopen() function. */
#define fdev_set_udata(stream, u) do { (stream)->udata = u; } while(0)

/** This macro retrieves a pointer to user defined data from a FILE
    stream object. */
#define fdev_get_udata(stream) ((stream)->udata)

Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... ). Такое поведение определяется стандартом языка C. Есть три зарезервированных переменных типа указатель на FILE. Это:
Код:
/**
   Stream that will be used as an input stream by the simplified
   functions that don't take a \c stream argument.

   The first stream opened with read intent using \c fdevopen()
   will be assigned to \c stdin.
*/
#define stdin (__iob[0])

/**
   Stream that will be used as an output stream by the simplified
   functions that don't take a \c stream argument.

   The first stream opened with write intent using \c fdevopen()
   will be assigned to both, \c stdin, and \c stderr.
*/
#define stdout (__iob[1])

/**
   Stream destined for error output.  Unless specifically assigned,
   identical to \c stdout.

   If \c stderr should point to another stream, the result of
   another \c fdevopen() must be explicitly assigned to it without
   closing the previous \c stderr (since this would also close
   \c stdout).
*/
#define stderr (__iob[2])

__iob[] - это массив стандартных указателей на "описатели потоков". Есть некоторое множество функций, работа с которыми подразумевает вывод с использованием этих трёх переменных. Я точно не помню, но вроде бы в стандарте есть функция, которая выводит сообщение об ошибке в поток вывода stderr. Одна из таких - assert(). Можно приравнять stderr = stdout и использовать в коде assert() для вывода диагностических сообщений в случае ошибок в коде. Только я не знаю как она в случае мк будет отрабатывать.

Если вернуться к printf(), то можно под отладчиком посмотреть какое значение имеет второй параметр в функции вывода символа, при использовании printf() и сравнить это значение с stdout. Думаю, это будет один и тот же указатель на структуру FILE.

Что касается указания второго параметра, то в структуре FILE дан явно его вид:
Код:
/*
 * This is an internal structure of the library that is subject to be
 * changed without warnings at any time.  Please do *never* reference
 * elements of it beyond by using the official interfaces provided.
 */
struct __file {
   char   *buf;      /* buffer pointer */
   unsigned char unget;   /* ungetc() buffer */
   uint8_t   flags;      /* flags, see below */
#define __SRD   0x0001      /* OK to read */
#define __SWR   0x0002      /* OK to write */
#define __SSTR   0x0004      /* this is an sprintf/snprintf string */
#define __SPGM   0x0008      /* fmt string is in progmem */
#define __SERR   0x0010      /* found error */
#define __SEOF   0x0020      /* found EOF */
#define __SUNGET 0x040      /* ungetc() happened */
#define __SMALLOC 0x80      /* handle is malloc()ed */
#if 0
/* possible future extensions, will require uint16_t flags */
#define __SRW   0x0100      /* open for reading & writing */
#define __SLBF   0x0200      /* line buffered */
#define __SNBF   0x0400      /* unbuffered */
#define __SMBF   0x0800      /* buf is from malloc */
#endif
   int   size;      /* size of buffer */
   int   len;      /* characters read or written so far */
   int   (*put)(char, struct __file *);   /* function to write one char to device */
   int   (*get)(struct __file *);   /* function to read one char from device */
   void   *udata;      /* User defined and accessible data. */
};

При инициализации этой структуры нужно передавать функцию вывода символа с двумя параметрами:
Код:
int   (*put)(char, struct __file *);   /* function to write one char to device */

Если мы используем printf(), то инициализация этого параметра происходит при присвоении stdout какого-то значения. Если мы используем fprintf(), то инициализация происходит, когда мы в эту функцию передаём указатель на созданный поток явно.

Re: помогите с потоком AVR

Ср апр 23, 2014 17:54:59

YS писал(а):Даже не могу сходу придумать, зачем такое могло бы понадобиться. Так что с этим похоже и правда мало кто сталкивался.

кое-кто сталкивался: http://www.simple-devices.ru/articles/7 ... console-io и чуть-чуть здесь: http://www.simple-devices.ru/articles/7 ... -interface
если МК не менее atmega8, то потоковый вывод уже становится вполне удобным и придает заметный комфорт в работе
uni писал(а):Чисто логически можно предположить следующее
предполагать не надо, можно быть уверенным :)
в Си printf - это функция/обертка для упрощения потокового вывода в stdout, фактически же она обращается к другой СТАНДАРТНОЙ функции fprintf, которая уже может выводить в любой поток. поэтому функция вывода симвода принимает указатель на этот самый поток - это фактически предписано стандартом: уметь выводить в любой поток, а не только stdout.

в применении к МК пример был приведен: можно выводить в разные UART (в некоторых МК их по два и более), кроме того весьма популярно работать с SD-картами - это уже реальные файлы, а еще есть дисплеи... то, что в простеньких проектах это кажется лишним, не должно сильно пугать: оптимизатор наверняка все приведет в нормальное состояние без лишней избыточности. во всяком случае если вы уж решились использовать форматированный вывод, отжирающий минимум 1,5-2К памяти программ, то пара байт на указатель потока вас и подавно не должна смущать :)))

Re: помогите с потоком AVR

Ср апр 23, 2014 22:38:12

Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... ). Такое поведение определяется стандартом языка C. Есть три зарезервированных переменных типа указатель на FILE.

Да это верно, именно так.
вот я и для этого делал: у меня был свой поток

//тут разные инклюды
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE );
main()
{
// вот это же мой поток выделен красным stdout_LCD
// и тут я присваиваю свой поток к stdout командой stdout=&stdout_LCD;
stdout=&stdout_LCD;
//ну потом можно уже
print("hello"); // а не fprint("hello");
вот я же явно присвоил командой stdout=&stdout_LCD; чтобы не пришлось писать fprintf();
}
с этим я согласен : "Функция printf() неявно использует переменную stdout для своей работы, т.е. она делает то же, что и fprintf( stdout, ... )."
Конечно хочу поблагодарить всех кто отозвался на мой вопрос.

но не пойму я следующие:
припустим я хочу через поток работать с lcd дисплеем и мне надо написать функцию-драйвер для printf().
т.е в строке static FILE stdout_LCD = FDEV_SETUP_STREAM(lcd_driver, NULL, _FDEV_SETUP_WRITE );
и вот тут при создании потока выделено красным я говорю что функция которая будет отвечать за отправку символа будет lcd_driver
и вот тут следующие вопросы
пример кода
# include подключаем нужные файлы
static FILE stdout_LCD = FDEV_SETUP_STREAM(lcd_driver, NULL, _FDEV_SETUP_WRITE); // вот тут lcd_driver это та функция которая отвечает за отправку символа или строки , но главное не это
потом
main()
{
print("hello");
}
void lcd_driver(char c)
{
// функция отправляет символ, описывать функцию думаю не надо.
}
все можно сказать код рабочий за том исключением что тут нет функ. инициализации дисплея.
ВОТ ТУТ САМОЕ ГЛАВНОЕ, ТАК КОМПИЛЯТОР РУГАЕТСЯ, Т.Е ЕСТЬ ВОРНИНГ
Warning 1 initialization from incompatible pointer type
но код работает, теперь я в этой функ. т.е void lcd_driver(char c) добавляю FILE *stream и получается void lcd_driver(char c, FILE * stream);
ВСЕ ВОРНИНГА НЕТ
КАК ЭТО ?
и соответственно я не могу теперь использовать функ. lcd_driver напрямую только через printf()
если вспомнить мой первый пост, т.е сам вопрос там есть пример кода только для uart то в функ.
int uart_putchar(char c, FILE *stream) Я ЯВНО ЖЕ НЕ ИСПОЛЬЗУЮ FILE *stream , зачем тогда второй параметр. может я что-то не допонял.
{

if (c == '\n')
uart_putchar('\r');
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;

Re: помогите с потоком AVR

Ср апр 23, 2014 23:08:25

Вы обязаны определять свою функцию так, как требует от вас компилятор. Просто сделайте два параметра и не парьтесь

Re: помогите с потоком AVR

Чт апр 24, 2014 00:04:36

Дело в том, что C - язык типизированный и указатели на функции у него тоже типизированные. Это сообщение говорит о том, что вы инициализируете поле в структуре __file не тем типом указателя на функцию. Я уже приводил выше цитату из заголовочника:
Код:
int   (*put)(char, struct __file *);   /* function to write one char to device */

Макрос, который создаёт поток, на самом деле просто присваивает свои параметры полям структуры __file. Функция для записи определена своим типом как принимающая два параметра. Кроме того, она ещё должна возвращать int (посмотрите на пример из заголовочника, там функция описана правильно, согласно типа).

Если вы опишите функцию по-другому, то это приведёт к нарушению в работе со стеком (странно, что вообще такое компилятор пропустил). Эта проблема называется - нарушение баланса стека и приводит к непредсказуемому поведению программы. Ни в коем случае нельзя так делать, как у ваш выше в примере. put_char() должна возвращать int и должна быть определена с двумя параметрами.

А, если что-то там и так "заработало", то это на свой страх и риск.

lcd_driver() во втором случае можно использовать и напрямую, нужно просто передать NULL в качестве второго параметра.

Re: помогите с потоком AVR

Чт апр 24, 2014 10:06:11

кое-кто сталкивался


ЫЫЫ, круто. :beer: Почитал исходники, красиво написано. :)

во всяком случае если вы уж решились использовать форматированный вывод, отжирающий минимум 1,5-2К памяти программ


Я лучше в эти 2K огромную таблицу синуса запишу. :)

Re: помогите с потоком AVR

Чт апр 24, 2014 17:03:35

Мы наверное про разный второй параметр говорим.
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); я не про NULL говорю

Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream

Re: помогите с потоком AVR

Чт апр 24, 2014 20:13:03

mideni писал(а):Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream

а почему вы, например, не интересуетесь, почему функция int tolower(int c);, которая служит для преобразования СИМВОЛОВ в качестве параметра и результата использует тип int, а не char, что как бы более логично?

вот так решили разработчики стандарта - такой ответ вас не устроит?

Re: помогите с потоком AVR

Вт авг 31, 2021 11:30:38

Мы наверное про разный второй параметр говорим.
static FILE stdout_LCD = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE ); я не про NULL говорю

Я просто хочу понять зачем в void lcd_driver(char c, FILE * stream); надо писать именно в этой функ. второй параметр FILE *stream


Вот и я по прошествии пяти лет с последнего сообщения хочу понять.

делаю проект на atmega 2560 в составе arduino mega 256.
загрузчик снес. пишу на СИ и ассемблере. приладил дисплей.
вывод букв и графики писал сам на ассемблере, потом обернул все в СИ-шную функцию и дальше
на СИ делаю. сначала пользовался строковой библиотекой: itoa и тп. чтобы строки выводить.
Сейчас дошел, что можно выводить через printf и тп. на экран(и в порт через USB консолью любой вывод получать), а переменные сама функция будет преобразовывать,
что гораздо удобней.
Функцию вывода строк на экран я писал еще до плотного знакомства с printf, поэтому сейчас её надо переделать. т.к. моя функция принимает строку с терминальным нулем, координаты курсора, цвет символа, цвет фона.
И вот, чтобы не городить огород очередной раз я понял (предварительно) что функция моя должна выводить один символ. а данные о остальных (вышеуказанных) параметрах должны храниться в udata. Получается очень удобно, эти параметры будут храниться в статичной структуре, между вызовами printf никуда не пропадать! а моя переписанная функция вывода посимвольная будет ориентироваться на эти данные и, когда курсор дойдет до конца строки, переносить (или не переносить а сдвигать весь экран влево, например) курсор. так как там указатель типа воид, туда я забью адрес своей структуры, где вся эта красота будет храниться.
А вот другие поля структуры FILE ведь тоже как то можно использовать?

Код:
char   *buf;      /* buffer pointer */
unsigned char unget;   /* ungetc() buffer */
и
 int   size;      /* size of buffer */
 int   len;      /* characters read or written so far */


знает кто-нибудь?

вот эти поля для записи адресов функций для вывода/ввода символов на/из устройство и, понятное дело, их нам менять не надо на ходу:
Код:
int   (*put)(char, struct __file *);   /* function to write one char to device */
int   (*get)(struct __file *);   /* function to read one char from device */


а вот к флагам можно и наверное подразумевается что мы можем обращаться?
видел кто нибудь инфу про это?

Re: помогите с потоком AVR

Вт авг 31, 2021 14:44:06

я бы не рекомендовал лезть в эту структуру, поскольку она используется в первую очередь для внутренних нужд lib-c и, если читать из неё вы можете безбоязненно, то писать в неё лучше не надо

Re: помогите с потоком AVR

Вт авг 31, 2021 15:24:37

не согласен. иначе зачем фунцкция вывода символа объявлена самими составителями
библиотеки с приемом параметра- адреса этой структуры. Однозначно, чтобы пользователь
использовал эти данные каким-либо образом.
/*
* This is an internal structure of the library that is subject to be
* changed without warnings at any time. Please do *never* reference
* elements of it beyond by using the official interfaces provided.
*/
перевод:
Это внутренняя структура библиотеки, которая может быть изменена без предупреждения в любое время.
Пожалуйста, никогда не ссылайтесь на его элементы, кроме как с помощью предоставленных официальных интерфейсов.

одно можно с уверенностью сказать что поле:
void *udata; /* User defined and accessible data. */

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

за остальные поля не знаю, нет примеров в интернете, во всяком случае я не нашел.
у всех функция эта не использует *stream никак.
Кстати как рекомендация тем кто пишет подобное, если вам структура не нужна, все равно придется ее указывать в
определении вашего варианта. если уж объявлена она ранее, то указывайте её. другое дело - функция имеет право никак не использовать
этот параметр.
Ответить