Написание драйверов устройств для микроконтроллеров, где определить контакты порта ввода-вывода?

Кажется, я всегда сталкиваюсь с этой дилеммой при написании низкоуровневого кода для микроконтроллеров. Я никогда не знаю, где объявить определения выводов, чтобы сделать код максимально пригодным для повторного использования.

В этом случае я пишу драйвер для интерфейса 8051 с 12-битным последовательным ЦАП MCP4922. Я не уверен, как и где я должен объявить определения контактов для CS (выбор микросхемы) и LDAC (защелка данных) для ЦАП. На данный момент нет заявленного в заголовочном файле драйвера.

Я провел много исследований, пытаясь найти лучший подход, но ничего не нашел.

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

Только фрагмент драйвера, чтобы вы поняли суть

/**
    @brief  This function is used to write a 16bit data word  to DAC B -12 data bit plus 4 configuration bits
    @param  dac_data A 12bit word 
    @param  ip_buf_unbuf_select Input Buffered/unbuffered  select bit. Buffered = 1; Unbuffered = 0
    @param  gain_select Output Gain Selection bit. 1 = 1x (VOUT = VREF * D/4096).  0 =2x (VOUT = 2 * VREF * D/4096)
*/
void MCP4922_DAC_B_TX_word(unsigned short int dac_data, bit ip_buf_unbuf_select, bit gain_select)
{                                             

    unsigned char low_byte=0, high_byte=0;
    CS = 0;                                               /**Select the chip*/

    high_byte |= ((0x01 << 7) | (0x01 << 4));            /**Set bit to select DAC A and Set SHDN bit high for DAC A active operation*/
    if(ip_buf_unbuf_select) high_byte |= (0x01 << 6);
    if(gain_select)         high_byte |= (0x01 << 5);

    high_byte |= ((dac_data >> 8) & 0x0F);
    low_byte |= dac_data;
    SPI_master_byte(high_byte);
    SPI_master_byte(low_byte);

    CS = 1;                                               
    LDAC = 0;                                             /**Latch the Data*/
    LDAC = 1;                                         
}

person volting    schedule 27.05.2010    source источник


Ответы (4)


Это то, что я сделал в аналогичном случае, этот пример для написания драйвера I²C:

// Structure holding information about an I²C bus
struct IIC_BUS
{
    int pin_index_sclk;
    int pin_index_sdat;
};

// Initialize I²C bus structure with pin indices
void iic_init_bus( struct IIC_BUS* iic, int idx_sclk, int idx_sdat );

// Write data to an I²C bus, toggling the bits
void iic_write( struct IIC_BUS* iic, uint8_t iicAddress, uint8_t* data, uint8_t length );

Все индексы контактов объявляются в заголовочном файле, зависящем от приложения, чтобы обеспечить быстрый обзор, например:

// ...
#define MY_IIC_BUS_SCLK_PIN 12
#define MY_IIC_BUS_SCLK_PIN 13
#define OTHER_PIN 14
// ...

В этом примере реализация шины I²C полностью переносима. Это зависит только от API, который может писать на контакты чипа по индексу.

Редактировать:

Этот драйвер используется следующим образом:

// main.c
#include "iic.h"
#include "pin-declarations.h"

main()
{
    struct IIC_BUS mybus;
    iic_init_bus( &mybus, MY_IIC_BUS_SCLK_PIN, MY_IIC_BUS_SDAT_PIN );

    // ...

    iic_write( &mybus, 0x42, some_data_buffer, buffer_length );
}
person Timbo    schedule 27.05.2010
comment
Это то, что я искал, спасибо. Я обобщу свое понимание, чтобы убедиться, что я понимаю ... если это нормально. 1) Структура для хранения информации о PIN-коде определяется в файле заголовка драйвера 2) Экземпляр структуры объявляется в вашем основном приложении 3) Информация о закреплении, определенная в другом заголовке, назначается структуре 4) Структура передается подпрограммам драйвера как необходимо. Это правильно? - person volting; 27.05.2010
comment
Я добавил пример использования для дальнейшего разъяснения, но вы поняли. Я не утверждаю, что это абсолютно лучший способ написать драйвер устройства для встраиваемой системы, но в моем приложении он показался подходящим. - person Timbo; 27.05.2010
comment
Спасибо, я понял, я вижу, что задание выполняется в iic_init_bus() - делает его красивым и аккуратным. Даже если это не самое лучшее, это определенно лучше, чем то, что у меня было. -практики встроенного программирования, чтобы избавить меня от вопросов такого типа... Еще раз спасибо - person volting; 27.05.2010
comment
Я только что узнал, что невозможно передать определения выводов на моей платформе keil+8051, поэтому я возвращаюсь к исходной точке, я думаю, что лучший способ (в данном случае) — это, вероятно, поместить определения выводов в отдельный заголовок конфигурации, если ни у кого нет идей получше. - person volting; 28.05.2010

В одном магазине, в котором я работал, определения выводов были помещены в заголовочный файл для конкретного процессора. В другом магазине я разбил заголовочные файлы на темы, связанные с модулями процессора, такими как DAC, DMA и USB. Главный включаемый файл для процессора включал все эти тематические заголовочные файлы. Мы могли бы смоделировать разные разновидности одного и того же процессора, включив в файл процессора разные заголовочные файлы модулей.

Вы можете создать заголовочный файл реализации. Этот файл будет определять контакты ввода-вывода в терминах файла заголовка процессора. Это дает вам один уровень абстракции между вашим приложением и оборудованием. Идея состоит в том, чтобы максимально свободно связать приложение с оборудованием.

person Thomas Matthews    schedule 27.05.2010
comment
Спасибо ... несколько достойных советов. Я склонялся к решению типа файла заголовка реализации, кажется, это единственное разумное решение для моей текущей платформы. Спасибо - person volting; 28.05.2010

Если о выводе CS нужно знать только драйверу, то объявление должно появляться не в шапке, а внутри самого модуля драйвера. Повторное использование кода лучше всего достигается путем сокрытия данных в максимально возможной области.

В случае, если внешнему модулю необходимо управлять CS, добавьте функцию доступа к модулю драйвера устройства, чтобы иметь единое управление. Это полезно, если во время отладки вам нужно знать, где и когда активируется контакт ввода/вывода; у вас есть только одна точка для применения инструментария или точек останова.

person Clifford    schedule 28.05.2010
comment
Да, только драйвер должен знать о выводе CS, но я хочу реализовать какой-то API, который позволил бы мне использовать другой вывод порта MCU в разных проектах или для переноса на другие платформы, не связываясь с модулем драйвера (что, очевидно, я придется изменить адрес CS ..) - person volting; 28.05.2010
comment
Вряд ли имеет значение, измените ли вы заголовок или .c, это все исходный код, и его в любом случае нужно будет перекомпилировать. - person Clifford; 29.05.2010
comment
На самом деле больше причин скрывать аппаратную специфику, тогда заголовочный файл останется одинаковым для всех реализаций. На другой платформе реализация, скорее всего, потребует изменений в любом случае; если вы последуете вашему предложению, изменения потребуются оба файла, а не только один. Заголовочный файл в идеале должен определять интерфейсы, а не внутренности. - person Clifford; 29.05.2010

Ответ с конфигурацией времени выполнения будет работать для приличного процессора, такого как ARM, PowerPC ... но здесь автор использует 8051. #define, вероятно, лучший способ. Вот как бы я разбил его:

blah.h:

#define CSN_LOW()   CS = 0
#define CSN_HI()    CS = 1
#define LATCH_STROBE() \
 do { LDAC = 0; LDAC = 1; } while (0)

blah.c:
#include <blah.h>
void blah_update( U8 high, U8 low ) 
{
   CSN_LOW();
   SPI_master_byte(high);
   SPI_master_byte(low);
   CSN_HI();
   LATCH_STROBE();
} 

Если вам нужно изменить определение контакта или перейти на другой ЦП, должно быть очевидно, где вам нужно обновить. И это также помогает, когда вам нужно настроить синхронизацию в шине (например, вставить задержку здесь и там), поскольку вам не нужно менять ее повсюду. Надеюсь, поможет.

person fseto    schedule 28.07.2010