воскресенье, 27 декабря 2009 г.

Библиотека для семисегментного дисплея BQ-M322RD

В связи с возникшей потребностью, была написана библиотека для работы с 4-х разрядным семисегментным дисплеем BQ-M322RD разработанным компанией Bright Led Electronics. Я также уверен что данная библиотека сможет работать и с другими аналогичными 4х-разрядными дисплеями, с общим анодом.
Внешний вид дисплея


Свечение сегментов тут практически не видно, так как сьёмка проходила на ярком дневном освещении. А так, не мешало бы приложить какое-нибудь тёмное стёклышко:)
Особенности данной библиотеки
При написании использовался ассемблер. Кроме этого, пытаясь максимально сократить размер кода, использованные алгоритмы были приближены к идеалу совершенства=) Для отображения цифр на сегментах дисплея был использован способ называемый динамической индикацией. Библиотека способна реализовывать этот режим двумя способами:
  1. Используя ресурсы таймера 0
  2. Используя функцию m322_loop() которую программа должна будет непрерывно вызывать в процессе своего выполнения
Библиотека занимает 264 байт памяти программ и 13 байт памяти данных при использовании первого способа, и соответственно 240 и 13 байт при использовании второго.
Выбрать один из способов можно отредактировав файл m322/conf.h

Примеры использования

Простой пример:
/*
 программа использует библиотеку откомпилированную
 с опцией #define USE_TIMER0
*/
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей
  m322_putn(1234); // вывести число 1234
  m322_setdot(0, TRUE); // зажечь точку в разряде 0
  for (;;)
    ;
}
Если библиотека откомпилирована вторым способом, то:
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей
  m322_putn(1234); // вывести число 1234 на семисегментный дисплей
  m322_setdot(0, TRUE); // зажечь точку в разряде 0
  for (;;)
    m322_loop(80); // цикл динамической индикации
}
В данном случае добавлен вызов функции m322_loop() в бесконечный цикл, которая реализует динамическую индикацию. В качестве аргумента ей надо указать некоторое число. Частота мерцания дисплея будет зависеть от этого значения. Чем выше значение - тем меньше частота мерцания. Для моего 16мГц МК на котором я тестировал данный пример, оказалось оптимальным значение 80. Если брать больше - будет заметно мерцание, если меньше - разряды могут "поплыть".
Более интересный пример:
/*
 программа использует библиотеку откомпилированную
 с опцией #define USE_TIMER0
*/
#define F_CPU 16000000ULL /* частота работы мк (для _delay) */
#include <util/delay.h>
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей

  uint16_t number = 0;
  uint8_t n_dot = 0;
  for (;;) {
    m322_setdot(n_dot, TRUE); // зажечь точку с заданным номером
    m322_putn(number); // вывести десятичное число на индикатор
    _delay_ms(100); // пауза 100мс
    m322_setdot(n_dot, FALSE); // погасить точку с заданным номером

    if (++n_dot == 5)
      n_dot = 0;
    if (++number == 10000)
      number = 0;
  }
}
Если попробовать выполнить программу на МК, то получим - на дисплее будет отображаться число, которое будет увеличиваться каждые 100 миллисекунд, а точка "зажигаясь" будет перемещаться циклически слева-направо.
Как настроить библиотеку под себя
Перед компиляцией библиотеки необходимо задать порты к которым подключён дисплей, отредактировав файл m322/conf.h:
//
// Порты используемые для доступа к отдельным сегментам
//
#define PORT_SEGDATA  _SFR_IO_ADDR(PORTD)
#define DDR_SEGDATA   _SFR_IO_ADDR(DDRD)
/* номера пинов для каждого отдельного сегмента */
#define NPIN_DP PD7 /* точка */
#define NPIN_G  PD6
#define NPIN_F  PD5
#define NPIN_E  PD4
#define NPIN_D  PD3
#define NPIN_C  PD2
#define NPIN_B  PD1
#define NPIN_A  PD0
//
// Порты используемые для доступа к отдельным разрядам
//
#define DDR_SEGCTL    _SFR_IO_ADDR(DDRB)
#define PORT_SEGCTL   _SFR_IO_ADDR(PORTB)
/* номера пинов для каждого отдельного разряда */
#define NPIN_SEGCTL0  PB4 /* D1 */
#define NPIN_SEGCTL1  PB1 /* D2 */
#define NPIN_SEGCTL2  PB2 /* D3 */
#define NPIN_SEGCTL3  PB3 /* D4 */
//
// Дополнительные настройки
//
// заккоментируйте следующую опцию если не хотите использовать
//  ресурсы системного таймера0 для организации динамической
//  индикации семисегментного дисплея:
/* использовать таймер */
#define USE_TIMER0

#ifdef USE_TIMER0
// частота мерцания семисегментного индикатора зависит
//  от коэффициента предделителя таймера0,
// и вычисляется по формуле f = F_CPU/(P*256*4), где:
//  F_CPU - тактовая частота мк, Гц
//  P - коэффициент предделителя
/* значение предделителя для таймера */
#define TIMER0_PRESCALER ((1<<CS00)|(1<<CS01)) /* предделитель 64 */
#endif // USE_TIMER0
Я думаю здесь комментарии излишни=)

Схематическое изображение дисплея:



Полный перечень поддерживаемых функций
Библиотека поддерживает все самые необходимые функции, а именно:
  • void m322_init()
    Выполняет инициализацию семисегментного дисплея (настройка портов, таймера).
  • void m322_puthd(uint8_t c, uint8_t n)
    Выводит шестнадцатеричную цифру n в разряд с номером c индикатора.
  • void m322_puthh(uint8_t n)
    Выводит шестнадцатеричное число n в старшую часть индикатора (разряды 0 и 1)
  • void m322_puthl(uint8_t n)
    Выводит шестнадцатеричное число n в младшую часть индикатора (разряды 2 и 3)
  • void m322_setdot(uint8_t c, uint8_t b)
    Задаёт точку в разряд с номером c индикатора. Если b = TRUE - зажечь, иначе погасить.
  • void m322_putn(uint16_t n)
    Выводит на индикатор беззнаковое десятичное число n
  • void m322_loop(uint16_t n)
    Цикл для организации динамической индикации без использования таймера0
Совместимость
Думаю что библиотека будет совместима с большинством микроконтроллеров AVR серии Mega (в моём случае был использован Atmega8).

Скачать
Исходный код библиотеки вместе с примером можно скачать здесь (или здесь).

воскресенье, 6 декабря 2009 г.

Библиотека для ЖК-дисплея WH1602b

После долгой кропотливой работы изучения документации и написания кода, родилась полезная и нужная для меня библиотека для работы с этим дисплеем. Возможно она окажется полезной кому-нибудь ещё :)
О дисплее
Дисплей этот я приобрёл на молотке около года тому назад, за почти астрономическую сумму в 450рэ. Ну тем не менее он устраивает меня во всём, и в быту неприхотлив=)
Особенности этой библиотеки
Код написан на ассемблере, и очень компактен. Занимает от 298 до 466 байт в памяти программ МК. В файле конфигурации wh1602b/conf.h можно дополнительно установить поддержку следующих функций:
  • Поддержка знакогенератора (298+52 байт)
  • Поддержка функции вывода десятичных чисел (298+66 байт)
  • Поддержка функции вывода шестнадцатеричных чисел (298+72 байт)
Основные функциональные возможности
  • Вывод строк, символов
  • Вывод чисел
  • Очистка дисплея
  • Управление курсором
  • Поддержка знакогенератора (до 8-ми знаков, согласно спецификации)
  • Настройка различных параметров работы
Не поддерживается:
  • Чтение памяти дисплея
  • Текущее положение курсора нельзя определить

Библиотека использует 4-битную шину для передачи данных.

Примеры использования

Простой пример:
#include "wh1602b/wh1602b.h"

int main(void)
{
  wh1602b_init(); // инициализировать модуль, порты
  // настроить параметры управления дисплеем
  wh1602b_displayctl(DC_DISPLAY_ON); // дисплей включён
  // задать число строк
  wh1602b_funcset(FS_LINES_2); // 2 строки
  // очистить область
  wh1602b_clear();
  // вывести строку на дисплей
  wh1602b_puts("Hello, World!");

  for (;;)
    ;
}
Результат:

Следующий пример демонстрирует возможность загрузки собственного знака:
#include "wh1602b/wh1602b.h"

// собственно массив представляющий собой
//  знак который надо загрузить
static const uint8_t window_char[8] = {
  0x1f, // 1 1 1 1 1
  0x11, // 1 0 0 0 1
  0x0e, // 0 1 1 1 0
  0x04, // 0 0 1 0 0
  0x0e, // 0 1 1 1 0
  0x15, // 1 0 1 0 1
  0x15, // 1 0 1 0 1
  0x00  // 0 0 0 0 0
};

int main(void)
{
  wh1602b_init(); // инициализировать модуль, порты
  // настроить параметры управления дисплеем
  wh1602b_displayctl(DC_DISPLAY_ON); // дисплей включён
  // задать число строк
  wh1602b_funcset(FS_LINES_2); // 2 строки
  // очистить область
  wh1602b_clear();
  // записать символ ассоциировав его с кодом 5
  wh1602b_genc(5, window_char);
  // вернуться в начало (команда аналогичная move).
  //   этот вызов является обязательным после вызова genc()
  wh1602b_home();
  // вывести строку на дисплей вместе с новым знаком :)
  wh1602b_puts("Крякозябла \5");

  for (;;)
    ;
}
Результат:

В качестве аргументов для wh1602b_puts и wh1602b_genc выступает указатель на строку расположенную в области RAM. Для того чтобы иметь возможность выводить строки из памяти программ, необходимо соответственно использовать wh1602b_putsP или wh1602b_gencP. Например:
#include "wh1602b/wh1602b.h"
#include <avr/pgmspace.h>

int main(void)
{
  <...>
  // вывести строку1 из памяти программ
  wh1602b_putsP(PSTR("Здравствуй"));
  // переместить курсор на заданную позицию (нумерация с нуля)
  wh1602b_move(1,4); // 2 строка, 5 столбец
  // вывести строку2 из памяти программ
  wh1602b_putsP(PSTR("Мир!"));
  <...>
}
Результат:


Как настроить библиотеку под себя
Для начала необходимо задать порты к которым подключён дисплей. Для этого необходимо отредактировать файл wh1602b/conf.h:
#define F_CPU 16000000 /* частота мк, Гц */

#define USE_PUTN /* включить поддержку wh1602b_putn() */
#define USE_PUTH /* включить поддержку wh1602b_puth() */
#define USE_GENC /* включить поддержку wh1602b_genc() */

/* ножки к которым подключены выводы RS, RW, EN */
#define NPIN_RS PB4
#define NPIN_RW PB3
#define NPIN_EN PB2

/* порты к которым подключены выводы RS, RW, EN */
#define PORT_RS _SFR_IO_ADDR(PORTB)
#define PORT_RW _SFR_IO_ADDR(PORTB)
#define PORT_EN _SFR_IO_ADDR(PORTB)
#define DDR_RS  _SFR_IO_ADDR(DDRB)
#define DDR_RW  _SFR_IO_ADDR(DDRB)
#define DDR_EN  _SFR_IO_ADDR(DDRB)

/* ножки к которым подключены выводы DB4-DB7 */
#define NPIN_DB7  PD7
#define NPIN_DB6  PD6
#define NPIN_DB5  PD5
#define NPIN_DB4  PD4

/* порты к которым подключены выводы DB4-DB7 */
#define PORT_DB7  _SFR_IO_ADDR(PORTD)
#define PORT_DB6  _SFR_IO_ADDR(PORTD)
#define PORT_DB5  _SFR_IO_ADDR(PORTD)
#define PORT_DB4  _SFR_IO_ADDR(PORTD)
#define DDR_DB7   _SFR_IO_ADDR(DDRD)
#define DDR_DB6   _SFR_IO_ADDR(DDRD)
#define DDR_DB5   _SFR_IO_ADDR(DDRD)
#define DDR_DB4   _SFR_IO_ADDR(DDRD)

Соответствующие ножки дисплея показаны на следующем рисунке:

Как компилировать программы написанные с использованием этой библиотеки
Существует ряд особенностей при компиляции программ использующих данную библиотеку. Дело в том что ЖК-дисплей понимает только строки написанные в своём собственном варианте кодировки. Поэтому компилировать надо с ключом -fexec-charset=wh1602b. При этом, в системе должен быть установлен специальный модуль для iconv - wh1602b. Готовые скомпилированный модуль можно скачать здесь (x86_64 и i686), или собрать его самостоятельно. Для установки необходимо скопировать файл WH1602B.so в папку с модулями gconv (у меня это /usr/lib/gconv), а также отредактировать файл /usr/lib/gconv/gconv-modules, добавив строчки:
# from      to      module    cost
alias WH-1602B//     WH1602B//
alias 1602B//       WH1602B//
module  WH1602B//    INTERNAL    WH1602B    1
module  INTERNAL    WH1602B//    WH1602B    1
Самостоятельная сборка такого модуля довольно кропотливая процедура, и будет описана в следующий раз.

Полный перечень поддерживаемых функций
  • void wh1602b_init()
    Выполняет инициализацию модуля дисплея.
  • void wh1602b_funcset(uint8_t t)
    Устанавливает число строк и размера шрифта. В качестве аргументов может служить комбинация одного или нескольких параметров:
    FS_LINES_2 - использовать две строки дисплея (по умолчанию одна строка, FS_LINES_1)
    FS_FONT_5x11 - использовать шрифт размером 5х11 (по умолчанию 5х8)
  • void wh1602b_entrymode(uint8_t t)
    Настраивает режима ввода. В качестве аргументов может служит комбинация одного или нескольких параметров:
    EM_CURSOR_DIR - использовать автоматическое перемещение курсора слева-направо
    EM_DISPLAY_SHIFT - разрешить смещение дисплея
  • void wh1602b_displayctl(uint8_t t)
    Устанавливает параметры управления дисплеем. В качестве аргументов может служит комбинация одного или нескольких параметров:
    DC_DISPLAY_ON - дисплей включён (по умолчанию выключен, DC_DISPLAY_OFF)
    DC_CURSOR_BLINK - использовать мигающий курсор
    DC_CURSOR_ON - использовать курсор
  • void wh1602b_puts(const char* s)
    Выводит строку s из RAM на дисплей.
  • void wh1602b_putsP(const char* s)
    Выводит строку s из памяти программ на дисплей.
  • void wh1602b_putn(uint16_t n)
    Выводит десятичное число n на дисплей.
  • void wh1602b_puth(uint8_t w, uint16_t h)
    Выводит шестнадцатеричное число h на дисплей. Ширина поля определяется параметром w
  • void wh1602b_genc(uint8_t n, const uint8_t* p)
    Записывает знак на который указывает p в знакогенератор. Если это последний записываемый знак, и при этом ожидается вывод на дисплей, то необходимо после выполнения genc вызвать wh1602b_home() или wh1602b_move().
  • void wh1602b_home()
    Возвращает курсор в начальное положение. Если дисплей при этом смещён то функция восстанавливает его.
  • void wh1602b_move(uint8_t r, uint8_t c)
    Перемещает курсор на заданную позицию (r - строка, c - столбец). Нумерация начинается с нуля.
  • void wh1602b_putc(uint8_t c)
    Выводит символ c на экран дисплея.
  • void wh1602b_clear()
    Очищает экран дисплея.
Скачать
Исходный код библиотеки вместе с примером можно скачать здесь.

понедельник, 30 ноября 2009 г.

Пишем драйвер для лампочки=) Часть 2.

В прошлый раз я писал как создавал драйвер для лампочки. Эта часть будет посвящена тому как я использовал свой драйвер в отдельном проекте.

Написание основного проекта
Свой проект я разместил в папке ~/hard_project, основной и единственный файл которого имеет следующее содержание:
// файл ~/hard_project/libdrv/main.c
#define F_CPU 16000000UL /* тактовая частота МК */
#include "leddrv/leddrv.h"

int main(void)
{
  led_init(); // инициализация лампочки
  for (;;) // бесконечный цикл
    led_blink_ms(500); // мигнуть с частотой 0,5сек
}
Этот код настолько прост и понятен что в дополнительных комментариях и не нуждается.

Упрощённую версию Makefile для сборки проекта можно скачать тут. Скомандовав в консоли, я и получил целевой hex файл:
$ make
avr-gcc -g -Wall -O2 -mmcu=atmega168 -o .main.o -c main.c
avr-gcc -g -Wall -O2 -mmcu=atmega168  -o .main.elf .main.o -static -Lleddrv/ -lleddrv
avr-objcopy -j .text -j .data -O ihex .main.elf main.hex

А затем и прошил свой МК:
$ make program
avrdude -v -c dapa -p atmega168 -P /dev/parport0 -b 115200 -U flash:w:main.hex

И вот оно, о чудо - работает!



Итог
Всё круто! Цель достигнута. В моих планах написать аналогичные библиотеки для ЖК-модуля и датчика температуры. Надеюсь это произойдёт в недалёкой перспективе:)

---
Скачать hard_project.tar.gz

воскресенье, 29 ноября 2009 г.

Пишем драйвер для лампочки=) Часть 1.

Хочу описать собственную идею (возможно она не нова), по которой я собираюсь создавать драйверы для различных аппаратных модулей, микросхем и тп.. Базируется она на основе использования статических библиотек, которые будут подключаться к основному проекту и представлять собой драйвер для какого-нибудь отдельно взятого модуля. Статическую библиотеку предлагаю писать на ассемблере, с целью сокращения объёма кода а также получения наивысшей производительности=) Такая библиотека, подключённая к основному проекту будет занимать минимум, при этом останется больше свободного места в памяти МК для написания основной логики.

В качестве примера можно рассмотреть тривиальный пример - драйвер для лампочки (индикатора). Данный пример подходит только для демонстрации самой идеи, а не её практического применения. Лампочка уж слишком простая вещь чтобы для неё писать целый драйвер. А вообще хотелось бы написать таковые для ЖК-дисплея, микросхемы ds1307, да много чего можно ещё придумать:) Главное желание, и свободное время. С последним у меня пока проблемы... Итак, приступим!

Подготовительная часть
1) Необходимо установить в систему пакеты gcc-avr, binutils-avr, и avr-libc. (если ещё не были установлены)
2) Вкратце изучить документацию по avr-libc. Там содержится очень много интересного(:
3) Создать папку для основного проекта, у меня это ~/hard_project.
4) Создать папку для проекта библиотеки, у меня это ~/hard_project/libdrv.

Написание библиотеки
Основной файл будет иметь следующее содержание:
;; файл ~/hard_project/libdrv/leddrv.S:
#include <avr/io.h>
#include "conf.h"

.global led_init ; void led_init()
; предварительная настройка порта
led_init:
  sbi _SFR_IO_ADDR(LED_DDR),LED_NPIN ; установить ножку на вывод данных
  ret

.global led_on ; void led_on()
; включение лампочки
led_on:
  sbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логическую 1 в порт
  ret

.global led_off ; void led_off()
; выключение лампочки
led_off:
  cbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логический 0 в порт
  ret

.global led_blink ; void led_blink(uint16_t f)
; мигание лампочки с заданной частотой
led_blink:
  sbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логическую 1 в порт
  push r24
  push r25
  rcall delay ; задержать выполнение
  cbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логический 0 в порт
  pop r25
  pop r24
  rcall delay ; задержать выполнение
  ret

; задержка на заданное значение
delay: ; до 770*65535/F_CPU, сек
   clr r0
0: com r0
1: dec r0
    brne 1b
    sbiw r24,1
    brne 0b ; 770 тактов за цикл
    ret

Здесь первый параметр для функции led_blink будет передаваться через регистр r24:r25, согласно соглашениям компилятора avr-gcc. Далее, заголовочный файл будет иметь следующее содержание:
// файл ~/hard_project/libdrv/leddrv.h:
#include <stdint.h>
#ifdef F_CPU
#define led_blink_ms(x) led_blink(((x)*(uint64_t)F_CPU)/(770*1000ul))
#endif

void led_init();
void led_on();
void led_off();
void led_blink(uint16_t f);

Здесь определён макрос led_blink_ms для удобства. При его вызове нужно указывать значение в миллисекундах, в отличии от led_blink, в которой указывается значение в условных единицах =) Последний файл проекта будет иметь следующее содержание:
// файл ~/hard_project/libdrv/conf.h:
#define LED_PORT PORTC
#define LED_DDR  DDRC
#define LED_NPIN PC1

Ну здесь наверное понятно, указываются параметры порта и ножки на которой будет сидеть лампочка =)

Это всё. Теперь необходимо собрать нашу бибиотеку. Упрощённую версию Makefile для сборки проекта можно скачать тут. Скомандовав в консоли, я и получил целевой файл libleddrv.a:
$Make
avr-gcc -Wall -x assembler-with-cpp -mmcu=atmega168 -o .leddrv.o -c leddrv.S
avr-ar rcs libleddrv.a .leddrv.o

В следующий раз я покажу как использовал эту библиотеку в своём проекте=)

---
Скачать исходники libdrv.tar.gz

среда, 18 ноября 2009 г.

Программа IBCrypt v1.00

IBCrypt - программа реализующая возможность шифрования информации с использованием блочного алгоритма на основе сети Фейштеля.
С использованием IBCrypt можно зашифровывать и расшифровывать файлы, а также простые текстовые сообщения написанные во встроенном текстовом редакторе. Более подбробно сведения об алгоритме шифрования описаны в справочном руководстве. Программа является моей курсовой работой по курсу «Информационная безопасность».

  

Лицензия: GNU GPL v3. Программа написана с использованием библиотеки Qt4

Скачать (с rapidshare.de):
  • Версия для GNU/Linux, с включёнными зависимостями: (i686, amd64)
  • Пакет для Arch Linux (i686, amd64)
  • Пакет для Debian/Ubuntu (i686, amd64)
  • Версия для Windows
  • Исходный код
Скачать (с альтернативного источника):
  • Версия для GNU/Linux, с включёнными зависимостями: (i686, amd64)
  • Пакет для Arch Linux (i686, amd64)
  • Пакет для Debian/Ubuntu (i686, amd64)
  • Версия для Windows
  • Исходный код

вторник, 17 ноября 2009 г.

Этот мир сошёл с ума...

Недавно я узнал что существуют девушки которые увлекаются (да именно увлекаются!) микроконтроллерами, юниксами, и си++. При этом они внешне выглядят вполне себе симпатично, как обычные девушки..

А если представить себе такую жену? Сидишь с ней знач в одной комнате. Она за ноутом пишет операционку для 8-битного AVR... Ну а ты рядом за другим ноутом, клепаешь PKGBUILD для своей недавно написанной на Си супернавороченной проги... Общаешься с ней через жабир, типа эта - "Жена, жрать хочу, давай заканчивай там..." :-D

Всё, нет пути. Мы все скоро умрём..

Мои впечатления о Chromium..

Давно хотел сменить браузер. Долгое время использовал оперу, и меня она устраивала. Она быстро работала, и хорошо интегрировалась с KDE/Oxygen при помощи темы opera_oxygen_project. Но в последнее время стала меня раздражать своими глюками. Во-первых: почему-то стало не возможным переход по ссылке до тех пор пока страница не загрузится. В соседних вкладках происходили так же подобные артефакты. Во-вторых: иногда происходили проблемы с отображением страницы - текст за текст налазил. Ну и в третьих: если долго не чистить историю, то со временем опера начинала жутко тормозить. Кроме того, шрифты без предварительного шаманства в настройках выглядили просто ужасно. Всё это меня достало. Файрфокс хоть и хорош своей расширяемостью, он очень плохо интегрируется с KDE. Да меня бы и устраивал Konqueror, если бы не пожирал огромное количество памяти..

Поэтому, решил я попробовать Chromium от гугла. И не пожалел. Загружается за 1-2сек. на моём нетбуке Acer One. Очень лёгкий, прорисовка страниц работает очень быстро. Глюков пока не заметил. Памяти жрёт приемлемо, но не больше чем опера. Есть конечно и некоторые минусы - шрифты уж слишком размазывает (может как-то лечиться?), ну и выглядит не настолько нативно как мне хотелось бы. И ещё меня насторожила вот эта ссылка, в которой сказано что Chromium отсылает "лишнюю" информацию гуглу. В связи с этим был создан проект Iron, который является форком хромиума, и с которого удалён "плохой" код. Будем на это надеятся...