- PVSM.RU - https://www.pvsm.ru -
Каждый опытный программист микропроцессоров сталкивался с написанием драйверов. При реализации небольших проектов или при переносе уже готового отлаженного кода на другой процессор, написание и отладка драйверов может занимать 50% и больше времени разработки. Причем процесс написания драйвера, для нового процессора, и состыковка существующего кода может быть очень не приятной из-за отсутствия структуры и общности в драйвере. Для программиста это становится нервной рутиной. Определим важные проблемы, при написании драйвера:
В данном посте я покажу, как можно ликвидировать эти проблемы и сделать процесс написания драйвера более приятной задачей на примере двух драйверов UART и DMA реализованных на языке С++. Для этого я сформулировал требования, для драйвера:
Итак, для начала, рассмотрим использование драйвера DMA, а затем реализацию. С помощью DMA выполняем передачу данных из ОЗУ в сдвиговый регистр UART и прием данных из сдвигового регистра UART в ОЗУ.
#include "iostm8l051f3.h"
#include "Driver_DMA.hpp"
char UartBuffer[128];
// создание объектов драйвера DMA
DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL1> DMAChannelTX; // DMA1 канал 1
DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL2> DMAChannelRX; // DMA1 канал 2
void func()
{
DMAChannelTX.global_disable(); // отключение всех каналов
// настройка передающего канала DMA
DMAChannelTX.set_periph_addr(&USART1_DR); // установка периферийного адреса
DMAChannelTX.set_memory0_addr(UartBuffer); // установка адреса в ОЗУ
DMAChannelTX = EnumDMA::DATABLOCK_8bit; // передача по-байтно
DMAChannelTX = EnumDMA::PRIORITY_MEDIUM; // приоритет средний
DMAChannelTX = EnumDMA::MEMPNT_INCREMENT; // инкрементирование адреса после передачи
DMAChannelTX = EnumDMA::MEMORY_TO_PHERIPH; // передача из ОЗУ в сдвиговый регистр UART
DMAChannelTX = EnumDMA::CIRCULAR_DISABLE; // циклическая передача отключена
// настройка приемного канала DMA
DMAChannelRX.set_periph_addr(&USART1_DR);
DMAChannelRX.set_memory0_addr(UartBuffer);
DMAChannelRX = EnumDMA::DATABLOCK_8bit;
DMAChannelRX = EnumDMA::PRIORITY_MEDIUM;
DMAChannelRX = EnumDMA::MEMPNT_INCREMENT;
DMAChannelRX = EnumDMA::PHERIPH_TO_MEMORY; // передача из сдвигового регистра UART в ОЗУ
DMAChannelRX = EnumDMA::CIRCULAR_DISABLE;
DMAChannelRX.set_number_of_transfers(sizeof(UartBuffer));
DMAChannelRX.channel_enable(); // текущий канал включен
DMAChannelTX.global_enable(); // включение всех каналов
}
Как видно из предыдущего листинга, код становится более понятный, для чтения, и самодокументируемым.
Интерфейс определен в структуре EnumDMA, в которой определены перечисления, которые используются в качестве аргументов методов драйвера.
struct EnumDMA
{
enum DMASel{
DMA1 = 0x5070
};
enum ChannelSel{
CHANNEL0 = 0x05,
CHANNEL1 = 0x0F,
CHANNEL2 = 0x19,
CHANNEL3 = 0x23
};
enum DataBlock{
DATABLOCK_8bit, DATABLOCK_16bit
};
enum ChannelPriority{
PRIORITY_LOW,
PRIORITY_MEDIUM,
PRIORITY_HIGH,
PRIORITY_VERYHIGH
};
enum MemoryPointerMode{
MEMPNT_DECREMENT,
MEMPNT_INCREMENT
};
enum CircularBufferMode{
CIRCULAR_DISABLE,
CIRCULAR_ENABLE
};
enum TransferType{ // только для канала 3
TRANS_TYPE_PHERIPH_TO_MEMORY,
TRANS_TYPE_MEMORY0_TO_MEMORY1
};
enum TransferDirection{
PHERIPH_TO_MEMORY,
MEMORY_TO_PHERIPH
};
enum InterruptSelection{
INTERRUPT_HALF_TRANSACTION_COMPLETE,
INTERRUPT_TRANSACTION_COMPLETE
};
enum InterruptVectors{
VECTOR_DMA1_CHANNEL0_HALF_TRANSACTION_COMPLETE = DMA1_CH0_HT_vector,
VECTOR_DMA1_CHANNEL0_TRANSACTION_COMPLETE = DMA1_CH0_TC_vector,
VECTOR_DMA1_CHANNEL1_HALF_TRANSACTION_COMPLETE = DMA1_CH1_HT_vector,
VECTOR_DMA1_CHANNEL1_TRANSACTION_COMPLETE = DMA1_CH1_TC_vector,
VECTOR_DMA1_CHANNEL2_HALF_TRANSACTION_COMPLETE = DMA1_CH2_HT_vector,
VECTOR_DMA1_CHANNEL2_TRANSACTION_COMPLETE = DMA1_CH2_TC_vector,
VECTOR_DMA1_CHANNEL3_HALF_TRANSACTION_COMPLETE = DMA1_CH3_HT_vector,
VECTOR_DMA1_CHANNEL3_TRANSACTION_COMPLETE = DMA1_CH3_TC_vector,
};
};
Перечисление DMASel позволяет выбрать модуль DMA, а перечисление ChannelSel определяет смещение в памяти между каналами DMA. В процессоре STM8L051F3 один модуль DMA, поэтому выбор не велик. Перечислению DMA1 присвоен адрес модуля DMA1.
Дизайн класса DriverDMA:
template <EnumDMA::DMASel DMA, EnumDMA::ChannelSel DMAChannel>
class DriverDMA
{
private:
...
struct DMA_struct // регистры модуля DMA влияющие на все каналы
{
volatile GCSR_REG GCSR; /*!< Global configuration and status register */
volatile GIR_REG GIR1; /*!< Global interrupt register 1 */
};
...
struct DMA_Channel_struct // регистры каналов модуля DMA
{
volatile CCR_REG CCR; /*!< CHx Control register */
volatile CSPR_REG CSPR; /*!< CHx Status & Priority register */
volatile unsigned char CNDTR; /*!< CHx Number of Bytes to Tranfer register */
volatile unsigned char CPARH; /*!< Peripheral Address High register */
volatile unsigned char CPARL; /*!< Peripheral Address Low register */
volatile unsigned char CM0EAR; /*!< Memory 0 Extended Address register (for channel3)*/
volatile unsigned char CM0ARH; /*!< Memory 0 Address High register */
volatile unsigned char CM0ARL; /*!< Memory 0 Address Low register */
};
u8 number_of_transfers;
u8 NumChannel;
public:
DriverDMA();
void operator= (EnumDMA::DataBlock);
void operator= (EnumDMA::ChannelPriority);
void operator= (EnumDMA::MemoryPointerMode);
void operator= (EnumDMA::CircularBufferMode);
void operator= (EnumDMA::TransferDirection);
void operator= (EnumDMA::TransferType); // только для канала 3
void global_enable();
void global_disable();
void channel_enable();
void channel_disable();
void set_number_of_transfers(const u16 trans_num);
void set_periph_addr(u8 volatile* addr);
void set_memory0_addr(u8* addr);
// ДОСТУПНЫЕ АДРЕСА ОТ 0х0000 до 0х1FFF
void set_memory1_addr(u8* addr); // только для канала 3
bool is_busy();
u16 get_amount_of_last_transation();
void interrupt_enable(EnumDMA::InterruptSelection);
void interrupt_disable(EnumDMA::InterruptSelection);
void interrupt_clear_pending_flag(EnumDMA::InterruptSelection);
};
Регистры модуля DMA определены в секции private, с помощью шаблона С++ выбирается модуль DMA и канал модуля.
Реализация конструктора класса и двух методов:
//----------------------------------------------------------------------------------------------
template <EnumDMA::DMASel DMA, EnumDMA::ChannelSel DMAChannel>
DriverDMA<DMA,DMAChannel>::DriverDMA()
{
CLK_PCKENR2_bit.PCKEN24 = 1; // DMA clock enable
__DMA->GCSR.bit.TO = 31;
NumChannel = (u8)DMAChannel;
}
//----------------------------------------------------------------------------------------------
template <EnumDMA::DMASel DMA, EnumDMA::ChannelSel DMAChannel>
void DriverDMA<DMA,DMAChannel>::operator= (EnumDMA::DataBlock db)
{
__DMACHANNEL->CSPR.bit.TSIZE = db;
}
//----------------------------------------------------------------------------------------------
template <EnumDMA::DMASel DMA, EnumDMA::ChannelSel DMAChannel>
void DriverDMA<DMA,DMAChannel>::global_enable()
{
__DMA->GCSR.bit.GEN = 1; // Global enable of DMA1
}
где __DMA и __DMACHANNEL определены следующим образом:
#define __DMA ((DMA_struct*) DMA)
#define __DMACHANNEL ((DMA_Channel_struct*) ((u32)DMA + (u32)DMAChannel))
На первый взгляд сложная конструкция:
__DMACHANNEL->CSPR.bit.TSIZE = db;
интерпретируется компилятором как 3 ассемблерных команды, а команда:
__DMA->GCSR.bit.GEN = 1;
занимает одну ассемблерную команду.
Что бы создать прерывание необходимо его разрешить, написать функцию прерывания, в которой нужно выполнить квитирование. Например создадим прерывание по окончанию транзакции DMA:
void func()
{
// разрешаем прерывание по завершению транзакции
DMAChannelTX.interrupt_enable(EnumDMA::INTERRUPT_TRANSACTION_COMPLETE)
}
#pragma vector = EnumDMA::VECTOR_DMA1_CHANNEL1_TRANSACTION_COMPLETE
__interrupt void DMA_transaction_complete()
{
// здесь обработка прерывания
DMAChannelTX.interrupt_clear_pending_flag(EnumDMA::INTERRUPT_TRANSACTION_COMPLETE)
}
Драйвер UART также реализован подобным образом с помощью класса, но без применения шаблона С++, так как модуль UART только один в данном процессоре. Драйвер UART использует два канала DMA, для приема и передачи данных. Так как пользователю драйвера доступ к DMA не нужен, то инициализируем DMA в секции private:
class DriverUART
{
private:
...
u8 UartBuffer[128];
DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL1> DMAChannelTX;
DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL2> DMAChannelRX;
...
Настройку DMA можно выполнить в конструкторе драйвера UART, таким образом, при создании объекта UART драйвера DMA будет сразу инициализировано и готово к работе.
//-----------------------------------------------------------------------
// Main DriverUART Constructor
//-----------------------------------------------------------------------
DriverUART::DriverUART(SELECTUART uart, u32 baud_rate, u32 sys_clock, PinconfigUART confPin)
{
CLK_PCKENR1_bit.PCKEN15 = 1; // UART clock enable
USART1_CR1_bit.USARTD = 0;
USART1_CR5_bit.DMAT = 1; // DMA enable transmitter
USART1_CR5_bit.DMAR = 1; // DMA enable receiver
DMAChannelTX.global_disable();
DMAChannelTX.set_periph_addr(&USART1_DR);
DMAChannelTX.set_memory0_addr(UartBuffer);
DMAChannelTX = EnumDMA::DATABLOCK_8bit;
DMAChannelTX = EnumDMA::PRIORITY_MEDIUM;
DMAChannelTX = EnumDMA::MEMPNT_INCREMENT;
DMAChannelTX = EnumDMA::MEMORY_TO_PHERIPH;
DMAChannelTX = EnumDMA::CIRCULAR_DISABLE;
DMAChannelRX.set_periph_addr(&USART1_DR);
DMAChannelRX.set_memory0_addr(UartBuffer);
DMAChannelRX = EnumDMA::DATABLOCK_8bit;
DMAChannelRX = EnumDMA::PRIORITY_MEDIUM;
DMAChannelRX = EnumDMA::MEMPNT_INCREMENT;
DMAChannelRX = EnumDMA::PHERIPH_TO_MEMORY;
DMAChannelRX = EnumDMA::CIRCULAR_DISABLE;
DMAChannelRX.set_number_of_transfers(sizeof(UartBuffer));
DMAChannelRX.channel_enable();
DMAChannelTX.global_enable();
set_sysclock(sys_clock, baud_rate);
USART1_CR2_bit.TCIEN = 1; // вкл. прерывания по окончанию передачи
USART1_CR2_bit.ILIEN = 1; // вкл. прерывание по приему
USART1_CR5_bit.EIE = 1; // вкл. прерывание по ошибке приема
__enable_interrupt();
close();
}
Реализация передачи данных:
void DriverUART::transmit(u8 * source, u16 size)
{
while(DMAChannelTX.is_busy()) ;
select_direction(TRANSMITION);
__disable_interrupt();
DMAChannelTX.global_disable();
DMAChannelTX.channel_disable();
DMAChannelTX.set_number_of_transfers(size);
DMAChannelTX.set_memory0_addr(source);
DMAChannelTX.global_enable();
DMAChannelTX.channel_enable();
__enable_interrupt();
}
Пример использования:
u8 buffer[] = "hello world!"
Uart1.transmit(buffer, sizeof(buffer));
По приему генерируется прерывание, в котором необходимо выполнять сброс приемного канала DMA:
void DriverUART::reception_handshake()
{
__disable_interrupt();
DMAChannelRX.global_disable();
DMAChannelRX.channel_disable();
received_size = DMAChannelRX.get_amount_of_last_transation();
DMAChannelRX.set_number_of_transfers(sizeof(UartBuffer));
DMAChannelRX.global_enable();
DMAChannelRX.channel_enable();
__enable_interrupt();
}
В прерывании можно получить указатель на внутренний буфер, в котором лежат полученные данные, и размер полученного пакета. Так как в данном случае прерывание создано внутри класса, то обработчик необходимо реализовать следующим образом:
//--------------------------------------------------------------------------
// Прерывание по приему
//--------------------------------------------------------------------------
void DriverUART::receive_handle()
{
u16 size;
u8* pnt;
Uart1.reception_handshake();
size = Uart1.get_received_size();
pnt = Uart1.get_pointer_on_internal_buffer();
// здесь может быть обработчик прерывания
}
Данные драйвера имеют четкую структуру, которая позволяет хорошо ориентироваться по ним, и благодаря которой драйвер лучше запоминается. Драйвер теперь воспринимается как объект, который можно настроить, через него можно что-то передать и что-то получить из него.
Создан интерфейс в виде перечней С++, который помогает лучше понять свойства и возможности драйвера и минимизировать общение с дата шитом процессора. Код благодаря данному интерфейсу становится самодокументируемым. Это все позволяет быстрее разобраться новичку в драйвере и вспомнить суть собственного кода опытному программисту.
Код драйвера можно использовать как шаблон, для написания аналогичного драйвера другого процессора данной линейки драйверов или других фирм производителей, значительно не меняя интерфейс.
Благодаря использованию шаблона С++ удалось значительно повысить скорость выполнения драйвера, но остаются затраты на вызов функции, которыми можно пренебречь, где не критична скорость выполнения.
Файлы драйверов DMA и UART можно скачать данной ссылке http://f-bit.ru/457683 [1].
В файле «Init_UART.cpp» пример использования драйвера UART.
Автор: SiYu
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/61885
Ссылки в тексте:
[1] http://f-bit.ru/457683: http://f-bit.ru/457683
[2] Источник: http://habrahabr.ru/post/225635/
Нажмите здесь для печати.