Свои библиотеки для MIK32

ejsanyo

Active member
Предлагаю делиться здесь своими наработками.
Начнём с простого: библиотека для текстового LCD-экрана, совместимого с HD44780, или как их довольно часто называют, "1602". Впрочем, при небольшой модификации, её можно приспособить и для других разрешений, вроде 20x4. Она используется, конечно же, в этом изделии, а вообще, в той или иной форме много где у меня.
В данном случае, прикрутил я экран на порт P1 через двунаправленный преобразователь уровней, но вот, например, мой коллега просто сажает экран сразу на 3,3В выходы, и у него и так всё работает. Кроме того, бывают экраны и с нативным 3,3В питанием. Такие точно есть у МЭЛТ-а, например.
Линии P1.0...P1.3 передают данные, P1.4...P1.7 - управление. И ещё бит остаётся, которым через ключ можно, например, подёргать подсветкой. Так что, вначале, настроим их:
C:
//main.c
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; //ноги данных для LCD-экрана
    GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_INPUT;
    GPIO_InitStruct.Pull = HAL_GPIO_PULL_UP;
    GPIO_InitStruct.DS = HAL_GPIO_DS_8MA;
    HAL_GPIO_Init(GPIO_1, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; //ноги управления для LCD-экрана
    GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
    GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
    GPIO_InitStruct.DS = HAL_GPIO_DS_8MA;
    HAL_GPIO_Init(GPIO_1, &GPIO_InitStruct);
Также не забудте настроить таймер ядра, он нам понадобится:
C:
//main.c

    //чтобы работать с функциями delay
    SCR1_TIMER_HandleTypeDef hscr1_timer;

    hscr1_timer.Instance = SCR1_TIMER;
    hscr1_timer.ClockSource = SCR1_TIMER_CLKSRC_INTERNAL;
    hscr1_timer.Divider = 0; //без деления частоты
    HAL_SCR1_Timer_Init(&hscr1_timer);
После чего можно будет сделать:
C:
//main.c
   LCD_init();
И пользоваться остальными функциями. А вот и библиотека:
C:
/*
LCD.c
Библиотека для работы с ЖК-дисплеем
на HD44780-совместимом контроллере
в 4-битном режиме
*/

#include "mik32_hal_gpio.h"
#include "xprintf.h"
#include "mik32_hal_scr1_timer.h"
#include "LCD.h"
//#include "LCD_Rus.h"

#define __NOP() __asm volatile ("ADDI x0, x0, 0")
#define wait_busy() while (LCD_busy() == GPIO_PIN_HIGH) __NOP()

//чтобы выбирать регистр команд или данных в контроллере LCD
typedef enum
{
COMMAND_REG,
DATA_REG
} LCD_reg;

extern SCR1_TIMER_HandleTypeDef hscr1_timer;

//буфер для вывода цифр текстом
uint8_t PrintBuf[xmax + 1];

//софтовая задержка на несколько команд
void nano_delay(void)
{
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();
}

//записывает полубайт по 4-битному интерфейсу
void LCD_write_4bit(uint8_t dat)
{
    uint32_t tmp;

    tmp = data_port->OUTPUT & 0xFFFFFFF0;
    data_port->OUTPUT = tmp | (dat & 0x0F);
    data_port->DIRECTION_OUT = 0x0F; //временно делаем ноги выходами
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_HIGH);
    nano_delay();
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_LOW);
    nano_delay();
    data_port->DIRECTION_IN = 0x0F;
}

//записывает байт в интерфейс
//второй параметр = регистр команд / регистр данных
void LCD_write(uint8_t dat, LCD_reg reg)
{
    if (reg == DATA_REG)
        HAL_GPIO_WritePin(control_port, RS_port, GPIO_PIN_HIGH);
    else
        HAL_GPIO_WritePin(control_port, RS_port, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(control_port, RW_port, GPIO_PIN_LOW);
    LCD_write_4bit(dat >> 4);
    LCD_write_4bit(dat);
}

//читаем состояние бита занятости
GPIO_PinState LCD_busy(void)
{
    GPIO_PinState tmp;

    HAL_GPIO_WritePin(control_port, RS_port, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(control_port, RW_port, GPIO_PIN_HIGH);
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_HIGH);
    nano_delay();
    tmp = HAL_GPIO_ReadPin(data_port, ready_pin);
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_LOW);
    nano_delay();
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_HIGH);
    nano_delay();
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_LOW);
    nano_delay();
    return tmp;
}

//полная очистка экрана
void LCD_clear(void)
{
    LCD_write(0x01, COMMAND_REG);
    wait_busy();
}

//поставить курсор в определённую позицию
void LCD_goto(uint8_t x, uint8_t y)
{

    if ((x <= xmax) & (y <= ymax))
        {
            switch (y)
                {
                    case 1:
                    x += 0x40;
                    break;

                    case 2:
                    x += 0x14;
                    break;

                    case 3:
                    x += 0x54;
                    break;
                }
            x |= 0x80; //дополняем до команды
            LCD_write(x, COMMAND_REG);
            wait_busy();
        }
}

//вывести один символ
void LCD_putchar(uint8_t dat)
{
    if (dat == '\n')
        LCD_goto(0, 1);
    else
    {
        LCD_write(dat, DATA_REG);
        wait_busy();
    }
}

//вывести строку символов
//заканчивающуюся "нуль-терминатором"
void LCD_puts(uint8_t *s)
{
    while (*s)
        {
            LCD_putchar(*s);
            s++;
        }
}

//вывести 32-битную переменную на экран
//в 16-ричном виде
void LCD_puthex(uint32_t a)
{
    xsprintf(PrintBuf, "0x%08X", a);
    LCD_puts(PrintBuf);
}

//вывести float на экран
void LCD_putfloat(float a)
{
    xsprintf(PrintBuf, "%7.3f", a);
    LCD_puts(PrintBuf);
}

//загружает из массива юзерские символы в CGRAM
//длина массива должна быть 64 байта!
void LCD_upload(const uint8_t *UserChars)
{
    uint8_t i;

    //устанавливаем указатель на начало CGRAM
    LCD_write(0x40, COMMAND_REG);
    wait_busy();
    //заливаем массив
    for (i = 0; i < 0x40; i++)
        {
            LCD_write(*UserChars, DATA_REG);
            wait_busy();
            UserChars++;
        }
}

//начальные настройки ног контроллера и режима дисплея
void LCD_init(void)
{
    //выставляем ноги в изначальные состояния
    HAL_GPIO_WritePin(control_port, RS_port, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(control_port, RW_port, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(control_port, E_port, GPIO_PIN_LOW);

    HAL_DelayMs(&hscr1_timer, 20);

    //настраиваем разрядность интерфейса
    LCD_write_4bit(0x03);
    HAL_DelayMs(&hscr1_timer, 1);
    LCD_write_4bit(0x03);
    HAL_DelayMs(&hscr1_timer, 1);
    LCD_write_4bit(0x03);
    HAL_DelayMs(&hscr1_timer, 1);
    LCD_write_4bit(0x02);
    HAL_DelayMs(&hscr1_timer, 1);
    LCD_write(0x28, COMMAND_REG);
    wait_busy();
    //настройка параметров
    LCD_write(0x06, COMMAND_REG);
    wait_busy();
    /*
    //заливаем юзерские символы
    LCD_upload(__cgram);
    */
    //чистим экран
    LCD_clear();
    //вкл. дисплей
    LCD_write(0x0C, COMMAND_REG);
    wait_busy();
}

//Вкл/выкл подсветку
void LCD_BL(uint8_t a)
{
    if (a)
        HAL_GPIO_WritePin(control_port, BL_port, GPIO_PIN_HIGH);
    else
        HAL_GPIO_WritePin(control_port, BL_port, GPIO_PIN_LOW);
}
C:
//LCD.h
//ноги для ЖК дисплея
#define RS_port GPIO_PIN_4
#define RW_port GPIO_PIN_5
#define E_port GPIO_PIN_6
#define data_port GPIO_1
#define control_port GPIO_1
#define ready_pin GPIO_PIN_3
//подсветка
#define BL_port GPIO_PIN_7

//максимальные позиции курсора
#define xmax 15
#define ymax 1

void LCD_init(void);
void LCD_clear(void);
void LCD_goto(uint8_t x, uint8_t y);
void LCD_putchar(uint8_t dat);
void LCD_puts(uint8_t *s);
void LCD_puthex(uint32_t a);
void LCD_putfloat(float a);
void LCD_BL(uint8_t a);
Конечно, чтобы печатать на экране float-ы, придётся использовать последнюю версию xprintf!

Как известно, HD44780 в дополнение ко встроенному шрифту позволяет загрузить до 8 пользовательских символов. Это, в частности, позволяет хотя бы частично русифицировать какой-нибудь дешёвый дисплей с Алиэкспресса, который изначально умеет говорить только по английски и по кетайски японски. Для этого у нас есть функция LCD_upload(). Вот пример такой "частичной русификации":
C:
//LCD_Rus.h

const uint8_t __cgram[]=
{
    0x1F, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E, 0x00, //Char 0x08 'Б'
    0x06, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x1F, 0x11, //Char 0x09 'Д'
    0x15, 0x15, 0x15, 0x0E, 0x15, 0x15, 0x15, 0x00, //Char 0x0A 'Ж'
    0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, //Char 0x0B 'И'
    0x07, 0x09, 0x09, 0x09, 0x09, 0x09, 0x11, 0x00, //Char 0x0C 'Л'
    0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, //Char 0x0D 'П'
    0x11, 0x11, 0x11, 0x19, 0x15, 0x15, 0x19, 0x00, //Char 0x0E 'Ы'
    0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11, 0x00, //Char 0x0F 'Я'
};
 
Последнее редактирование:

ejsanyo

Active member
А вот пара слов о том, как я пользую UART через прерывания. Когда-то я подсмотрел основную идею в коде, который генерировал мастер проектов в CodeVision AVR. И с тех пор она в том или ином виде используется много где.
Сделаем два файла:
C:
/*
uart_int.c

Реализует кольцевые буферы по типу таких, как было сделано в Codevision
*/
#include "mik32_hal_irq.h"
#include "mik32_hal_wdt.h"
#include "uart_int.h"

#define __NOP() __asm volatile ("ADDI x0, x0, 0")

//указатели и счётчики кольцевых буферов
#if UART_BUFFER_SIZE < 256
static uint8_t rx_wr_index = 0, rx_rd_index = 0, rx_counter = 0;
static uint8_t tx_wr_index = 0, tx_rd_index = 0, tx_counter = 0;
#else
static uint16_t rx_wr_index = 0 ,rx_rd_index = 0, rx_counter = 0;
static uint16_t tx_wr_index = 0, tx_rd_index = 0, tx_counter = 0;
#endif

//флаги переполнения
static bool rx_buffer_overflow = false;

//сами буфера
static uint8_t Tx_Buf[UART_BUFFER_SIZE], Rx_Buf[UART_BUFFER_SIZE];

extern WDT_HandleTypeDef hwdt;

//Чтобы удобнее включать и выключать прерывания UART
void TxIntEn(bool enable)
{
    uint32_t control1;

    control1 = UART_0->CONTROL1;
    if (enable)
        UART_0->CONTROL1 = control1 | UART_CONTROL1_TXEIE_M;
    else
        UART_0->CONTROL1 = control1 & ~UART_CONTROL1_TXEIE_M;
}

void RxIntEn(bool enable)
{
    uint32_t control1;

    control1 = UART_0->CONTROL1;
    if (enable)
        UART_0->CONTROL1 = control1 | UART_CONTROL1_RXNEIE_M;
    else
        UART_0->CONTROL1 = control1 & ~UART_CONTROL1_RXNEIE_M;
}

//вставить это в обработчик прерываний по передаче UART (когда буфер опустел)
void TxInt(void)
{
  UART_WriteByte(UART_0, Tx_Buf[tx_rd_index]);
  tx_rd_index++;
  if (tx_rd_index == UART_BUFFER_SIZE) tx_rd_index = 0;
  tx_counter--;
  if (tx_counter == 0) //если перекинули последний байт из буфера
      TxIntEn(false); //прерывания больше не нужны
}

//вставить это в обработчик прерываний по приёму UART
void RxInt(void)
{
  //тянем данные в буфер
  if (rx_buffer_overflow)
  {
      UART_ReadByte(UART_0);
  }
  else
  {
    Rx_Buf[rx_wr_index] = UART_ReadByte(UART_0);
    rx_wr_index++;
    if (rx_wr_index == UART_BUFFER_SIZE) rx_wr_index = 0;
    rx_counter++;
    if (rx_counter >= UART_BUFFER_SIZE) rx_buffer_overflow = true;
  }
}

//пихает байт в буфер
void UART_putchar(uint8_t c)
{
    while (tx_counter >= UART_BUFFER_SIZE) HAL_WDT_Refresh(&hwdt, WDT_TIMEOUT_DEFAULT); //__NOP(); //ждём пока буфер не освободится

    if ((tx_counter > 0) | (UART_IsTransmissionFinished(UART_0) == false))
    {
        HAL_EPIC_MaskLevelClear(HAL_EPIC_UART_0_MASK); //чтобы прерывания не ломали структуру буфера
        Tx_Buf[tx_wr_index] = c;
        tx_wr_index++;
        if (tx_wr_index == UART_BUFFER_SIZE) tx_wr_index = 0;
        tx_counter++;
        TxIntEn(true);
        HAL_EPIC_MaskLevelSet(HAL_EPIC_UART_0_MASK);
    }
    else
        UART_WriteByte(UART_0, c);
}

//вытащить байт из буфера
//если буфер пустой - возвращает 0
uint8_t UART_getchar(void)
{
    uint8_t data;

    if (rx_counter == 0)
        return 0x00;

    HAL_EPIC_MaskLevelClear(HAL_EPIC_UART_0_MASK);
    data = Rx_Buf[rx_rd_index];
    rx_rd_index++;
    if (rx_rd_index == UART_BUFFER_SIZE) rx_rd_index = 0;
    rx_counter--;
    rx_buffer_overflow = false;
    HAL_EPIC_MaskLevelSet(HAL_EPIC_UART_0_MASK);
    return data;
}

//получить длину принятой в буфер последовательности
#if UART_BUFFER_SIZE < 256
uint8_t UART_GetRXcounter(void)
#else
uint16_t UART_GetRXcounter(void)
#endif
{
    return rx_counter;
}

C:
//uart_int.h

#include "mik32_hal.h"
#include "uart_lib.h"

//длина буферов UART-а
#define UART_BUFFER_SIZE 64

void RxInt(void);
void TxInt(void);
void UART_putchar(uint8_t c);
uint8_t UART_getchar(void);

#if UART_BUFFER_SIZE < 256
uint8_t UART_GetRXcounter(void);
#else
uint16_t UART_GetRXcounter(void);
#endif

Делаем в main.c обработчик прерываний
C:
//main.c

#include "uart_int.h"

extern unsigned long __TEXT_START__; //это "метка" начала кода?!

void trap_handler() //сам обработчик всех прерываний
{
    /* Прерывание от UART 0 */
    if (EPIC_CHECK_UART_0())
    {
        if (UART_IsRxFifoFull(UART_0)) //какие-то данные принялись
        {
            RxInt();
        }
        if (UART_0->CONTROL1 & UART_CONTROL1_TXEIE_M) //было разрешено прерывание
        if (UART_IsTxBufferFreed(UART_0)) //и буфер передачи пустой
        {
            TxInt();
        }
    }

    HAL_EPIC_Clear(0xFFFFFFFF); //сбрасываем все флаги прерываний
}

А настройки делаем такие:
C:
//main.c

UART_Init(UART_0, (uint32_t)278, UART_CONTROL1_TE_M | UART_CONTROL1_RE_M | UART_CONTROL1_M_8BIT_M
                | UART_CONTROL1_RXNEIE_M, 0, UART_CONTROL3_OVRDIS_M, true); //115200 б/с

HAL_EPIC_Clear(0xFFFFFFFF); //чистим все флаги прерываний
HAL_EPIC_MaskLevelSet(HAL_EPIC_UART_0_MASK);
HAL_IRQ_EnableInterrupts();

И дальше в коде можно использовать UART_putchar() и UART_getchar(). Можно подшить UART_putchar() к xprintf с помощью макроса xdev_out(). В принципе и UART_getchar() подшить с помощью xdev_in() также не возбраняется (хоть мне пока это не требовалось), но для этого придётся его слегка изменить:
Код:
//если буфер пустой - возвращает -1
int16_t UART_getchar(void)
{
    uint8_t data;

    if (rx_counter == 0)
        return -1;

    HAL_EPIC_MaskLevelClear(HAL_EPIC_UART_0_MASK);
    data = Rx_Buf[rx_rd_index];
    rx_rd_index++;
    if (rx_rd_index == UART_BUFFER_SIZE) rx_rd_index = 0;
    rx_counter--;
    rx_buffer_overflow = false;
    HAL_EPIC_MaskLevelSet(HAL_EPIC_UART_0_MASK);
    return (int16_t)data;
}
 
Обвязка для работы без HAL с примерами: https://gitflic.ru/project/rabidrabbit/mik32_amur_simple
Изменён crt0.S - trap_handler расположен в Сишном коде и размещается по адресу 0x20000C0, объявлен как как __attribute__ ((interrupt,used,section(".trap_handler_ram"))) void trap_handler(void), т.е. при обработке прерывания управление передаётся сразу в обработчик, который работает из ОЗУ и выполняется максимально быстро (если не лезть из него к константным данным в SPIFI и не дёргать функции, расположенные в SPIFI).
Все примеры собираются со скриптом компоновщика под размещение в SPI Flash и проверены на плате ACE-UNO от ELRON. Добавлен варианта загрузчика, который работает сильно быстрее ELRONовского, а также в нём отключается кэширование данных из SPIFI, что заметно ускоряет выполнение кода, который часто обращается к константным данным.
В настоящий момент оно ещё дополняется.
Большинство присутствующих "библиотечных" функций выдраны из MUSL C Library.
 
Последнее редактирование:

Yuri

New member
Подскажите, пожалуйста, а обработчик обязательно помещать в main.c, или его можно разместить в отдельном файле (по аналогии с обработчиками у stm32)
 
Совсем не обязательно, спокойно можно размещать в отдельном файле. В примерах обработчик размещён в main.c только для "наглядности", просто потому, что в разных примерах могут быть задействованы прерывания от разного оборудования.
 

Yuri

New member
Совсем не обязательно, спокойно можно размещать в отдельном файле. В примерах обработчик размещён в main.c только для "наглядности", просто потому, что в разных примерах могут быть задействованы прерывания от разного оборудования.
Это понятно, но на всякий случай уточнил. Спасибо!
 
Сверху