STM32, C++ и FreeRTOS. Разработка с нуля. Часть 3 (LCD и Экраны)

от автора


Введение

В двух предыдущих частях STM32, C++ и FreeRTOS. Разработка с нуля. Часть 1 и STM32, C++ и FreeRTOS. Разработка с нуля. Часть 2 мною уже были реализованы требования SR0, SR7, SR4 и SR6. Опять нужно вспомнить, какие вообще требования есть.
SR0: Устройство должно измерять три параметра (иметь три переменных): Температуру микропроцессора, Напряжение VDDA, Напряжение с переменного резистора
SR1: Устройство должно выводить значение этих переменных на индикатор.
SR2: Единицы измерения для Температуры микропроцессора — градусы Цельсия, для остальных параметров — вольты.
SR3: При нажатии на кнопку 1, на индикаторе должен показываться экран со следующей измеряемой переменной,
SR4: При нажатии на кнопку 1 Светодиод 1 должен изменять свое состояние
SR5: При нажатии на кнопку 2, на индикаторе должен поменяться режим отображения переменных с постоянного показывания переменной на последовательное (менять экраны раз в 1.5 секунды) при следующем нажатии с последовательного на постоянное,
SR6: При нажатии на кнопку 2 светодиод 2 должен менять свое состояние.
SR7: Светодиод 3 должен моргать раз в 1 секунду.

Значит остались самые «вкусные» требования связанные c отображением всей измеренной информацие на индикаторе: SR1, SR2, SR3, SR5. Ну что же начнем.

Разработка: драйвер индикатора

Начну с драйвера индикатора. Помнится (все тот же пресловутый проект 8-летней давности), я уже писал вывод на индикатор и особо проблем у меня это не вызвало, однако то был простой микроконтроллер, а тут целый «процессор» с кучей настроек. Хорошо, что существует множество подробных статей, как правильно настроить драйвер индикатора и иже с ним. Например публикация HallEffect Работа с ЖК индикатором на отладочной плате STM32L-Discovery, плюс я поковырялся в исходниках демо проекта для платы Olimex, ну и конечно же прочитал документацию, дабы сразу понять все «хитрости» современных микроконтроллеров. И это дало свои плоды, я познакомился с такой замечательной вещью как Bit Banding, очень дотсупно это описано тут: Что такое Bit Banding на примере stm32.
Подняв технический скилс, я сел за очередное рисование, описывать класс драйвера cLcdDriver. Для начала посмотрим как выглядит индикатор:
image.

Как видно в нем 2 строки маленькая(верхняя) и БОЛЬШАЯ нижняя и еще куча всяких сегментов, которые мне нужны :). Также я решил использовать только нижнюю БОЛЬШУЮ строку. Её будет достаточно, чтобы вывести значение переменных и единицы измерения. Ну вот и определились, с тем куда и что выводить, и после рисования получился класс:

image

Теперь настало время реализации. Для начала нужно было настроить кучу портов, а именно 47 🙂 на альтернативную функцию LCD. Но я упорный, также пришлось переключить тактирование LCD на источник от внешнего генератора, потому что от внутреннего у меня при отладке через раз он не работал. Все это было впихнуто в __low_level_init().

настройка индикатора в __low_level_init

   /Переключаем тактирование LCD на внешний НЧ генератор, а то че-то     //от внутрннего иногда глючит       RCC->CSR |= RCC_CSR_RTCRST;    RCC->CSR &= ~RCC_CSR_RTCRST;    RCC->CSR |= RCC_CSR_LSEON;    while(!(RCC->CSR&RCC_CSR_LSERDY))    {    }       RCC->CSR |= RCC_CSR_RTCSEL_LSE;    //настраиваем порты индикатора     //Настраиваем  PA.08 на LCD COM0     //РА.08 на альтернативную функцию см. стр 174. CD00240194.pdf    GPIOA->MODER  |= GPIO_MODER_MODER8_1;    //PA.08 на LCD СОМ0, см стр.189 CD00240194.pdf    GPIOA->AFR[1]|= GPIO_AF_LCD;             //Настраиваем  PA.09 на LCD COM1     //РА.09 на альтернативную функцию см. стр 174,189 CD00240194.pdf    GPIOA->MODER  |= GPIO_MODER_MODER9_1;    GPIOA->AFR[1] |= GPIO_AF_LCD << PIN9_SHIFT;             //Настраиваем  PA.10 на LCD COM2     //РА.10 на альтернативную функцию см. стр 174,189 CD00240194.pdf    GPIOA->MODER  |= GPIO_MODER_MODER10_1;    GPIOA->AFR[1] |= GPIO_AF_LCD << PIN10_SHIFT;     //Настраиваем  PB.09 на LCD COM3     //РB.09 на альтернативную функцию см. стр 174,189 CD00240194.pdf    GPIOB->MODER  |= GPIO_MODER_MODER9_1;    GPIOB->AFR[1] |= GPIO_AF_LCD << PIN9_SHIFT;       //Настраиваем  PA.01 на LCD SEG0     //РА.01 на альтернативную функцию см. стр 174,189 CD00240194.pdf    GPIOA->MODER  |= GPIO_MODER_MODER1_1;    GPIOA->AFR[0] |= GPIO_AF_LCD << PIN1_SHIFT;        //Настраиваем  PA.02 на LCD SEG1     //РА.02 на альтернативную функцию см. стр 174189 CD00240194.pdf    GPIOA->MODER  |= GPIO_MODER_MODER2_1;    GPIOA->AFR[0] |= GPIO_AF_LCD << PIN2_SHIFT;     ...    //Настраиваем  PD.02 на LCD SEG43    //РD.02 на альтернативную функцию см. стр 174,189 CD00240194.pdf    GPIOD->MODER  |= GPIO_MODER_MODER2_1;    GPIOD->AFR[0] |= GPIO_AF_LCD << PIN2_SHIFT;    //Настройка LCD: DUTY = 1/4, BIAS=1/4, MUX_SEG = disable    //VSEL = 0 (внутренний источник питания)    //PRESCALLER= 1/2, DIVIDER = ck_ps/20, BLINK =0, BLINKF=0, CC=VLCD4, DEAD = 0    //PON = 3, UDDIE = 0,SOFIE = 0, HD = 0, см. стр 377-378 CD00240194.pdf    //DUTY 1/4     // Прескаллер на 1/2, делитель на 20 см стр 378. CD00240194.pdf    LCD->FCR = LCD_FCR_PS_1 | (LCD_FCR_DIV_0 | LCD_FCR_DIV_2) |  (LCD_FCR_PON_0 | LCD_FCR_PON_1) | LCD_FCR_CC_2 | LCD_FCR_CC_1;       // Ожидание пока установится регистр FCR    while (!(LCD->SR & LCD_SR_FCRSR ))    {     }    LCD->CR |= (LCD_CR_DUTY_1 | LCD_CR_DUTY_0);    //Активируем дисплей    LCD->CR |= LCD_CR_LCDEN; 

Идем дальше, аппаратный встроенный в микроконтроллер драйвер индикатора имеет свою RAM, нужно писать в неё, а потом разом выводить на индикатор. Тут на помощь, и пришел Bit Banding, который позволил создать pTableSegs массив из 166 элементов (по количеству) укзателей на адреса tU32 ячеек — являющихся отображением битов в регистрах RAM. Установка 1 или 0 в такую tU32 ячейку автоматически устанавливает или сбрасывает бит в регистре RAM, на который она смапирована. Адреса этих ячеек высчитываются по мудренной формуле, взятой мною из документации и демопроекта.

/ Используем BitBanding см. стр 49. CD00240194.pdf #define SEG_MASK(seg)    (seg & (32-1)) #define SEG_EL(seg,com)  (volatile tU32 *)(PERIPH_BASE + 0x2000000 +  ((0x2400 + 0x14 + ((com*2) + ((seg<32)?0:1))*4)*32) + SEG_MASK(seg)*4)) //таблица с адресами для bitBandinga для каждого сегмента volatile tU32* cLcdDriver::pTableSegs[] = {   SEG_EL(39,3), // +    0   SEG_EL(39,0), // -    1   SEG_EL(37,3), // 1A   2   SEG_EL(37,2), // 1B   3   SEG_EL(37,1), // 1C   4   SEG_EL(37,0), // 1D   5   SEG_EL(39,1), // 1E   6   SEG_EL(39,2), // 1F   7     ...      SEG_EL(5 ,1), // 11F  165   SEG_EL(4 ,1), // 11G  166 }; 

Дальше все уже было дело техники. Сегменты БОЛЬШОЙ СТРОКИ составлены вот так:

// Сегменты больших символов на нижней(Большой) строке                //       _______a_______ //      |\      |      /|    //      f  h    j    k  b    |col //      |    \  |  /    | //      |___g__\ /__m___|       //      |      / \      |    |  //      e    q  p  n    c //      |  /    |    \  | //      |/______d______\|    |dp #define SEG_A ((tU32)1<<0) #define SEG_B ((tU32)1<<1) #define SEG_C ((tU32)1<<2) ... #define SEG_DP ((tU32)1<<14) #define SEG_COL ((tU32)1<<15) 

Соотвественно чтобы вывести скажем букву С, нужно зажечь a,d,e,f сегменты поэтому С выглядит вот так:

#define Symbol_C  (SEG_A | SEG_D | SEG_E | SEG_F) 

Еще немного пришлось повозиться с точкой, чтобы выводить её в то же место куда выводиться цифра. Ну вобщем после довольно продолжительной и нудной работы вышла вот такая портянка 🙂

lcddriver.h

#include "types.h"            //Стандартные типы проекта tU32, tBoolean class cLcdDriver {   public:     explicit cLcdDriver(void);     void showBigString(const char* pStr);   private:     void updateDisplay(void);     tBoolean isReady(void);     void showBigSymbol(const tU32 digitPlace, const char character, const tBoolean bDot);     static volatile tU32* pTableSegs[];     static const tU32 charToLcdSymbol[];     static const tU32 bigDigitOffset[]; }; 

lcddriver.cpp

#include "lcddriver.h"       // Определение класса  #include <stm32l1xx.h>        //Регистры STM32 #include "susuassert.h"       //для ASSERT #include "types.h"            //для типов tPort, tU16, tU8 #include "bitutil.h"          //для макросов работы с битами SETBIT, CLRBIT #include <stddef.h>           //для NULL    #define BIG_SYMBOLS_COUNT 7  //количество символов в нижней(большой) строке  // Используем BitBanding см. стр 49. CD00240194.pdf // доп инфа тут: https://plus.google.com/115316880241890152471/posts/M7tzhpQiC9M #define SEG_MASK(seg)    (seg & (32-1)) #define SEG_EL(seg,com)  (volatile tU32 *)(PERIPH_BASE + 0x2000000 + ((0x2400 + 0x14 + ((com*2) + ((seg<32)?0:1))*4)*32) + (SEG_MASK(seg)*4)) #define SEG_A ((tU32)1<<0) #define SEG_B ((tU32)1<<1) #define SEG_C ((tU32)1<<2) #define SEG_D ((tU32)1<<3) #define SEG_E ((tU32)1<<4) #define SEG_F ((tU32)1<<5) #define SEG_G ((tU32)1<<6) #define SEG_H ((tU32)1<<7) #define SEG_J ((tU32)1<<8) #define SEG_K ((tU32)1<<9) #define SEG_M ((tU32)1<<10) #define SEG_N ((tU32)1<<11) #define SEG_P ((tU32)1<<12) #define SEG_Q ((tU32)1<<13) #define SEG_DP ((tU32)1<<14) #define SEG_COL ((tU32)1<<15) // Сегменты больших символов на нижней(Большой) строке                //       _______a_______ //      |\      |      /|    //      f  h    j    k  b    |col //      |    \  |  /    | //      |___g__\ /__m___|       //      |      / \      |    |  //      e    q  p  n    c //      |  /    |    \  | //      |/______d______\|    |dp #define Symbol_20  (tU32)0 #define Symbol_21  (tU32)0 #define Symbol_22  (tU32)0 #define Symbol_23  (tU32)0 #define Symbol_24  (tU32)0 #define Symbol_25  (tU32)0 #define Symbol_26  (tU32)0 #define Symbol_27  (tU32)0 #define Symbol_28  (tU32)0 #define Symbol_29  (tU32)0 #define Symbol_2A  (tU32)0 #define Symbol_2B (SEG_J | SEG_M | SEG_P | SEG_G) //символ '+' #define Symbol_2C (SEG_DP) //символ ',' #define Symbol_2D (SEG_J | SEG_M) //символ '-' #define Symbol_2E (SEG_DP) //символ '.' #define Symbol_2F (SEG_K | SEG_Q) //символ '/' #define Digit_0 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_H | SEG_N) #define Digit_1 (SEG_B | SEG_C) #define Digit_2 (SEG_A | SEG_B | SEG_G | SEG_E | SEG_D | SEG_M) #define Digit_3 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_G | SEG_M) #define Digit_4 (SEG_F | SEG_B | SEG_C | SEG_G | SEG_M) #define Digit_5 (SEG_A | SEG_F | SEG_G | SEG_C | SEG_D | SEG_M) #define Digit_6 (SEG_A | SEG_F | SEG_G | SEG_C | SEG_D | SEG_E | SEG_M) #define Digit_7 (SEG_A | SEG_B | SEG_C) #define Digit_8 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G | SEG_M) #define Digit_9 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G | SEG_M) #define Symbol_3A (SEG_DP) // символ ':' #define Symbol_3B (SEG_COL) // символ ';' #define Symbol_3C (SEG_K | SEG_N) // символ '<' #define Symbol_3D (SEG_A | SEG_G | SEG_M) //символ '=' #define Symbol_3E (SEG_H | SEG_Q) //символ '>' #define Symbol_3F  (tU32)0 //символ '?' будет у нас пробелом :) #define Symbol_40 (SEG_D | SEG_E | SEG_F | SEG_A | SEG_B | SEG_M| SEG_J) // '@' #define Symbol_A  (SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G | SEG_M) #define Symbol_B  (SEG_A | SEG_K | SEG_N | SEG_D | SEG_E | SEG_G | SEG_F) #define Symbol_C  (SEG_A | SEG_D | SEG_E | SEG_F) #define Symbol_D  (SEG_A | SEG_B | SEG_C | SEG_D | SEG_J | SEG_P)  #define Symbol_E  (SEG_A | SEG_G | SEG_M | SEG_D | SEG_E | SEG_F) #define Symbol_F  (SEG_A | SEG_G | SEG_M | SEG_E | SEG_F) #define Symbol_G  (SEG_A | SEG_N | SEG_D | SEG_E | SEG_F) #define Symbol_H  (SEG_F | SEG_E | SEG_G | SEG_M | SEG_B | SEG_C) #define Symbol_I  (SEG_G | SEG_P) #define Symbol_J  (SEG_B | SEG_C | SEG_D) #define Symbol_K  (SEG_F | SEG_E | SEG_G | SEG_K | SEG_N) #define Symbol_L  (SEG_F | SEG_E | SEG_D) #define Symbol_M  (SEG_E | SEG_F | SEG_H | SEG_K | SEG_B | SEG_C) #define Symbol_N  (SEG_E | SEG_F | SEG_H | SEG_N | SEG_B | SEG_C) #define Symbol_O   Symbol_D #define Symbol_P  (SEG_E | SEG_F | SEG_A | SEG_B | SEG_M | SEG_G) #define Symbol_Q  (SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_N) #define Symbol_R  (SEG_A | SEG_B | SEG_M | SEG_N | SEG_G | SEG_E | SEG_F) #define Symbol_S  (SEG_A | SEG_F | SEG_G | SEG_M | SEG_C | SEG_D) #define Symbol_T  (SEG_A | SEG_J | SEG_P) #define Symbol_U  (SEG_F | SEG_E | SEG_D | SEG_C | SEG_B) #define Symbol_V  (SEG_H | SEG_N | SEG_C | SEG_B) #define Symbol_W  (SEG_F | SEG_E | SEG_Q | SEG_N | SEG_C | SEG_B) #define Symbol_X  (SEG_H | SEG_Q | SEG_N | SEG_K) #define Symbol_Y  (SEG_H | SEG_K | SEG_P) #define Symbol_Z  (SEG_A | SEG_K | SEG_Q | SEG_D) #define BIG_SYMBOL_SIZE (16)  //1 символ 16 сегментов,включая двоеточие и точку // 1 символ начинается со смещения 2 (+ и - пропускается), дальше см таблицу pTableSegs #define Big_Digit_0_offset  (tU32)2   #define Big_Digit_1_offset  (tU32)18  #define Big_Digit_2_offset  (tU32)34 #define Big_Digit_3_offset  (tU32)50 #define Big_Digit_4_offset  (tU32)66 #define Big_Digit_5_offset  (tU32)82 #define Big_Digit_6_offset  (tU32)97 // У нас 7 цифр на дисплее, это массив для сдвига в количестве сегментов для каждой из цифр.  const tU32 cLcdDriver::bigDigitOffset[] =  {   Big_Digit_0_offset, Big_Digit_1_offset,    Big_Digit_2_offset, Big_Digit_3_offset,   Big_Digit_4_offset, Big_Digit_5_offset,   Big_Digit_6_offset   };  //таблица конвертации (ASCI кода буквы - ASCI код ' '(пробела)) в значение на сегментах индикатора const tU32 cLcdDriver::charToLcdSymbol[] =  {   Symbol_20,   Symbol_21, Symbol_22, Symbol_23, Symbol_24, Symbol_25,   Symbol_27, Symbol_27, Symbol_28, Symbol_29, Symbol_2A,   Symbol_2B, Symbol_2C, Symbol_2D, Symbol_2E, Symbol_2F,   Digit_0, Digit_1, Digit_2, Digit_3, Digit_4,   Digit_5, Digit_6, Digit_7, Digit_8, Digit_9,   Symbol_3A, Symbol_3B, Symbol_3C, Symbol_3D,   Symbol_3D, Symbol_3F, Symbol_40, Symbol_A,   Symbol_B, Symbol_C, Symbol_D, Symbol_E, Symbol_F,   Symbol_G, Symbol_H, Symbol_I, Symbol_J, Symbol_K,   Symbol_L, Symbol_M, Symbol_N, Symbol_O, Symbol_P,   Symbol_Q, Symbol_R, Symbol_S, Symbol_T, Symbol_U,   Symbol_V, Symbol_W, Symbol_X, Symbol_Y, Symbol_Z   }; //таблица с адресами для bitBandinga для каждого сегмента volatile tU32* cLcdDriver::pTableSegs[] = {   SEG_EL(39,3), // +    0   SEG_EL(39,0), // -    1   SEG_EL(37,3), // 1A   2   SEG_EL(37,2), // 1B   3   SEG_EL(37,1), // 1C   4   SEG_EL(37,0), // 1D   5   SEG_EL(39,1), // 1E   6   SEG_EL(39,2), // 1F   7   SEG_EL(38,2), // 1G   8   SEG_EL(38,3), // 1H   9   SEG_EL(36,3), // 1J   10   SEG_EL(36,2), // 1K   11   SEG_EL(36,1), // 1M   12   SEG_EL(36,0), // 1N   13   SEG_EL(38,0), // 1P   14   SEG_EL(38,1), // 1Q   15   SEG_EL(35,0), // 1DP  16   SEG_EL(35,3), // 2COL 17    SEG_EL(33,3), // 2A   18   SEG_EL(33,2), // 2B   19   SEG_EL(33,1), // 2C   20   SEG_EL(33,0), // 2D   21   SEG_EL(35,1), // 2E   22   SEG_EL(35,2), // 2F   23   SEG_EL(34,2), // 2G   24   SEG_EL(34,3), // 2H   25   SEG_EL(32,3), // 2J   26   SEG_EL(32,2), // 2K   27   SEG_EL(32,1), // 2M   28   SEG_EL(32,0), // 2N   29   SEG_EL(34,0), // 2P   30   SEG_EL(34,1), // 2Q   31   SEG_EL(31,0), // 2DP  32   SEG_EL(31,3), // 3COL 33    SEG_EL(29,3), // 3A   34   SEG_EL(29,2), // 3B   35   SEG_EL(29,1), // 3C   36   SEG_EL(29,0), // 3D   37   SEG_EL(31,1), // 3E   38   SEG_EL(31,2), // 3F   39   SEG_EL(30,2), // 3G   40   SEG_EL(30,3), // 3H   41   SEG_EL(28,3), // 3J   42   SEG_EL(28,2), // 3K   43   SEG_EL(28,1), // 3M   44   SEG_EL(28,0), // 3N   45   SEG_EL(30,0), // 3P   46   SEG_EL(30,1), // 3Q   47   SEG_EL(27,0), // 3DP  48   SEG_EL(27,3), // 4COL 49    SEG_EL(25,3), // 4A   50   SEG_EL(25,2), // 4B   51   SEG_EL(25,1), // 4C   52   SEG_EL(25,0), // 4D   53   SEG_EL(27,1), // 4E   54   SEG_EL(27,2), // 4F   55   SEG_EL(26,2), // 4G   56   SEG_EL(26,3), // 4H   57   SEG_EL(24,3), // 4J   58   SEG_EL(24,2), // 4K   59   SEG_EL(24,1), // 4M   60   SEG_EL(24,0), // 4N   61   SEG_EL(26,0), // 4P   62   SEG_EL(26,1), // 4Q   63   SEG_EL(23,0), // 4DP  64   SEG_EL(23,3), // 5COL 65    SEG_EL(21,3), // 5A   66   SEG_EL(21,2), // 5B   67   SEG_EL(21,1), // 5C   68   SEG_EL(21,0), // 5D   69   SEG_EL(23,1), // 5E   70   SEG_EL(23,2), // 5F   71   SEG_EL(22,2), // 5G   72   SEG_EL(22,3), // 5H   73   SEG_EL(20,3), // 5J   74   SEG_EL(20,2), // 5K   75   SEG_EL(20,1), // 5M   76   SEG_EL(20,0), // 5N   77   SEG_EL(22,0), // 5P   78   SEG_EL(22,1), // 5Q   79   SEG_EL(19,0), // 5DP  80   SEG_EL(19,3), // 6COL 81    SEG_EL(17,3), // 6A   82   SEG_EL(17,2), // 6B   83   SEG_EL(17,1), // 6C   84   SEG_EL(17,0), // 6D   85   SEG_EL(19,1), // 6E   86   SEG_EL(19,2), // 6F   87   SEG_EL(18,2), // 6G   88   SEG_EL(18,3), // 6H   89   SEG_EL(16,3), // 6J   90   SEG_EL(16,2), // 6K   91   SEG_EL(16,1), // 6M   92   SEG_EL(16,0), // 6N   93   SEG_EL(18,0), // 6P   94   SEG_EL(18,1), // 6Q   95   SEG_EL(15,0), // 6DP  96    SEG_EL(13,3), // 7A   97   SEG_EL(13,2), // 7B   98   SEG_EL(13,1), // 7C   99   SEG_EL(13,0), // 7D   100   SEG_EL(15,1), // 7E   101   SEG_EL(15,2), // 7F   102   SEG_EL(14,2), // 7G   103   SEG_EL(14,3), // 7H   104   SEG_EL(12,3), // 7J   105   SEG_EL(12,2), // 7K   106   SEG_EL(12,1), // 7M   107   SEG_EL(12,0), // 7N   108   SEG_EL(14,0), // 7P   109   SEG_EL(14,1), // 7Q   110    SEG_EL(1 ,3), // A1   111   SEG_EL(1 ,2), // A2   112   SEG_EL(1 ,1), // A3   113   SEG_EL(1 ,0), // A4   114    SEG_EL(2 ,0), // BRBL 115   SEG_EL(2 ,3), // B0   116   SEG_EL(2 ,2), // B1   117   SEG_EL(2 ,1), // B2   118    SEG_EL(0 ,3), // PL   119   SEG_EL(0 ,2), // P0   120   SEG_EL(0 ,1), // P1   121   SEG_EL(0 ,0), // P2   122   SEG_EL(43,0), // P3   123   SEG_EL(43,1), // P4   124   SEG_EL(43,2), // P5   125   SEG_EL(43,3), // P6   126   SEG_EL(42,3), // P7   127   SEG_EL(42,2), // P8   128   SEG_EL(42,1), // P9   129   SEG_EL(42,0), // PR   130    SEG_EL(3 ,0), // AL   131   SEG_EL(3 ,1), // AU   132   SEG_EL(3 ,2), // AR   133   SEG_EL(3 ,3), // AD   134    SEG_EL(15,3), // SB   135    SEG_EL(10,0), // 8A   136   SEG_EL(10,1), // 8B   137   SEG_EL(10,2), // 8C   138   SEG_EL(11,3), // 8D   139   SEG_EL(11,2), // 8E   140   SEG_EL(11,0), // 8F   141   SEG_EL(11,1), // 8G   142   SEG_EL(10,3), // 8P   143    SEG_EL(8 ,0), // 9A   144   SEG_EL(8 ,1), // 9B   145   SEG_EL(8 ,2), // 9C   146   SEG_EL(9 ,3), // 9D   147   SEG_EL(9 ,2), // 9E   148   SEG_EL(9 ,0), // 9F   149   SEG_EL(9 ,1), // 9G   150   SEG_EL(8 ,3), // 10P  151   SEG_EL(7 ,3), // 10COLON  152    SEG_EL(7 ,0), // 10A  153   SEG_EL(6 ,0), // 10B  154   SEG_EL(6 ,2), // 10C  155   SEG_EL(6 ,3), // 10D  156   SEG_EL(7 ,2), // 10E  157   SEG_EL(7 ,1), // 10F  158   SEG_EL(6 ,1), // 10G  159    SEG_EL(5 ,0), // 11A  160   SEG_EL(4 ,0), // 11B  161   SEG_EL(4 ,2), // 11C  162   SEG_EL(4 ,3), // 11D  163   SEG_EL(5 ,2), // 11E  164   SEG_EL(5 ,1), // 11F  165   SEG_EL(4 ,1), // 11G  166 }; /******************************************************************************* * Function:  constructor * Description:  ******************************************************************************/ cLcdDriver::cLcdDriver(void)  {   this->updateDisplay(); } /******************************************************************************* * Function:  showBigString * Description: Выводит информацию в нижнюю(большую)строку на индикаторе.  ******************************************************************************/ void cLcdDriver::showBigString(const char* pStr) {   tU32 digitPlace = 0;   tBoolean bDot = FALSE;   //флаг установки точки   const char *pNextChar = pStr;   pNextChar++;   //заполняем регистры RAM индиактора новыми данными   //проверяем следующий символ, если он равено точке или запятой   //ставим флаг необходимости установки сегмента точки в TRUE   //Пустые символы заполняются пробелами   while (digitPlace < BIG_SYMBOLS_COUNT)   {     if (( *pNextChar == '.' ) || (*pNextChar == ','))     {       bDot = TRUE;     }     if ((*pStr != '.') && (*pStr != ','))     {       if (*pStr != NULL)       {         this->showBigSymbol(digitPlace, *pStr, bDot);       }       else       {         this->showBigSymbol(digitPlace, ' ', FALSE);       }              digitPlace++;     }     pStr++;     pNextChar++;     bDot = FALSE;   }   //Запрашиваем обновления дисплея   this->updateDisplay(); } /******************************************************************************* * Function:  showBigSymbol * Description: Записывает большой(нижней строки) символ в память индикатора,  *              но не выводит его индикатор ******************************************************************************/ void cLcdDriver::showBigSymbol(const tU32 digitPlace,                                 const char character, const tBoolean bDot) {   ASSERT(character > 0);   ASSERT(character < 61);   volatile tU32 **p_data = &this->pTableSegs[this->bigDigitOffset[digitPlace]];   tU32 mask = charToLcdSymbol[character - ' '];   //Если надо установить точку, устанавливаем доп сегмент точки   if (bDot == TRUE)    {     mask |= SEG_DP;   }   // устанавливаем биты в регистрах памяти LCD->RAM через битБендинг   for(tU32 i = 0, j = 1; i < BIG_SYMBOL_SIZE; i++, j <<= 1)   {     if(mask & j)     {       **p_data = 1;     }     else     {       **p_data = 0;     }     ++p_data;   }  } /******************************************************************************* * Function:  isReady * Description: Проверяем готовность индикатора ******************************************************************************/ tBoolean cLcdDriver::isReady(void) {   tBoolean result = FALSE;    if (!CHECK_BITS_SET(LCD->SR,LCD_SR_UDR))     {     result = TRUE;   }   return result; } /******************************************************************************* * Function:  updateDisplay * Description: Выполняет запрос на обновление дисплея, вызвывается каждый раз *              после обновления памяти LCD ******************************************************************************/ void cLcdDriver::updateDisplay(void) {   SETBIT(LCD->SR, LCD_SR_UDR); } 

Проверял я это просто, создавая напрямую объект драйвера индикатора в main() функции вот так:

cLcdDriver *pLcdDriver = new cLcdDriver(); pLcdDriver->showBigString("H.E.L.L.O"); 

Разработка: Логика вывода инофрмации на индикатор

Ну вот и все с драйвером покончено. Пора приступить к логике вывода информации на индикатор. Я решил немного обдумать и не кидаться сразу делать активный класс, а подумать как формировать экраны. Поскольку у нас 3 разных переменных, которые и выводиться то должны по разному, то как миниму должно быть три разных класса экранов для каждой из переменной. Но управлять хотелось ими как одним. Поэтому нужно было вначале нарисовать единый интерфейс для всех экранов. Все экраны как миниму должны иметь доступ к драйверу индикатора и переменным. Драйвер у нас это cLcdDriver класс, а все переменные находятся в контейнере cVariableDirector, ну и экран должен уметь рисовать сам себя. А теперь рисуем мы 🙂
image
Самое время нарисовать наследников для вывода экранов Температуры, Vdda и Триммера. Они просто должны реализовывать один виртуальный метод show(), и потому все выглядит очень тривиально:
image
При реализации, выбирал метод преобразования tF32 в строку, и решил не париться, использовал старого знакомого sprintf, можно было бы написать класс утилитку для конвертации, но не стал, а потому реализация выглядит так:

iscreen.h

#include "types.h"               //Стандартные типы проекта #include "lcddriver.h"           //для cLcdDriver #include "variablesdirector.h"   // для cVariableDirector  class iScreen {   public:     explicit iScreen(cLcdDriver *pLcdDriver, const cVariablesDirector *pVariablesDirector);     virtual void show(void) = 0;   protected:     cLcdDriver *pLcdDriver;     const cVariablesDirector *pVariablesDirector;     }; 

iscreen.cpp

#include "iscreen.h"          // описание класса #include "susuassert.h"       // для ASSERT /******************************************************************************* * Function:  constructor * Description:  ******************************************************************************/ iScreen::iScreen(cLcdDriver *pLcdDriver,                   const cVariablesDirector *pVariablesDirector)   {   ASSERT(pLcdDriver != NULL);   ASSERT(pVariablesDirector != NULL);     this->pLcdDriver =  pLcdDriver;      this->pVariablesDirector = pVariablesDirector; } 

screentemperature.h

#include "lcddriver.h"        //для cLcdDriver #include "variablesdirector.h"      // для cVariableDirector  #include "iscreen.h"          //для iScreen class cScreenTemperature : public iScreen {   public:     explicit cScreenTemperature(cLcdDriver *pLcdDriver, const cVariablesDirector *pVariablesDirector);     void show(void);     }; 

screentemperature.cpp

#include "screentemperature.h"  // описание класса #include "types.h"              // стандартные типы проекта #include <stdio.h>              // для sprintf /******************************************************************************* * Function:  constructor * Description:  ******************************************************************************/ cScreenTemperature::cScreenTemperature(cLcdDriver *pLcdDriver,  const cVariablesDirector *pVariablesDirector) : iScreen(pLcdDriver, pVariablesDirector)   { } /******************************************************************************* * Function:  show * Description: Показывает текущую температуру ******************************************************************************/ void cScreenTemperature::show(void)   {   char str[10];   tF32 value = this->pVariablesDirector->pTemperature->getValue();   sprintf(str, "T %4.1f C", value);   this->pLcdDriver->showBigString(str);      } 

screentrimmer.h

include "lcddriver.h"          //для cLcdDriver #include "variablesdirector.h"  // для cVariableDirector  #include "iscreen.h"            //для iScreen class cScreenTrimmer : public iScreen {   public:     explicit cScreenTrimmer(cLcdDriver *pLcdDriver, const cVariablesDirector *pVariablesDirector);     void show(void); }; 

screentrimmer.cpp

#include "screentrimmer.h"      // описание класса #include "types.h"              // стандартные типы проекта #include <stdio.h>              // для sprintf /******************************************************************************* * Function:  constructor * Description:  ******************************************************************************/ cScreenTrimmer::cScreenTrimmer(cLcdDriver *pLcdDriver, const cVariablesDirector *pVariablesDirector) : iScreen(pLcdDriver, pVariablesDirector)   {  } /******************************************************************************* * Function:  show * Description: Показывает текущее значение на переменном резисторе ******************************************************************************/ void cScreenTrimmer::show(void)   {   char str[10];   tF32 value = this->pVariablesDirector->pTrimmer->getValue();   sprintf(str, "P %3.2f V", value);   this->pLcdDriver->showBigString(str);      } 

Ну что же теперь нужен класс для управления всем этим хозяйством семейством, а назовем его cScreenManager. Вспомним про требования: SR3: При нажатии на кнопку 1, на индикаторе должен показываться экран со следующей измеряемой переменной. Ага значит нам нужен метод NextScreen(). А следующее требование говорит: SR5: При нажатии на кнопку 2, на индикаторе должен поменяться режим отображения переменных с постоянного показывания переменной на последовательное (менять экраны раз в 1.5 секунды) при следующем нажатии с последовательного на постоянное. Не трудно угадать, что нужный метод должен называться NextMode() 🙂
Кроме того этот cScreenManager должен создавать все типы экранов (у нас их три cTemperatureScreen, cTrimmerScreen и сVddaScreen), но работать с ними должен через единый интерфейс, поэтому все созданные экраны будут храниться в массиве iScreen *pScreen[SCREEN_NUM];
И так снова рисуем для наглядности:
image
И неотходя от кассы реализуем:

screenmanager.h

#include "types.h"            //Стандартные типы проекта #include "iscreen.h"          //для iScreen #define SCREEN_NUM              (tU32)3 #define TEMPERATURE_SCREEN_ID   (tU32)0 #define TRIMMER_SCREEN_ID       (tU32)1 #define VDDA_SCREEN_ID          (tU32)2 typedef enum {   SM_single = 0,   SM_sequence = 1   }tScreenMode; class cScreenManager {   public:     explicit cScreenManager(cLcdDriver *pLcdDriver,                              const cVariablesDirector *pVariablesDirector);     void nextScreen(void);     void nextMode(void);     void show(void);   private:      iScreen *pScreen[SCREEN_NUM];     iScreen *pCurrentScreen;     tU32 screenId;     tScreenMode eMode; }; 

screenmanager.cpp

#include "screenmanager.h"          // описание класса #include "screentemperature.h"      //для ScreenTemperature #include "screentrimmer.h"          //для сScreenTrimmer #include "screenvdda.h"             //для сScreenVdda #include "susuassert.h"             // для ASSERT /******************************************************************************* * Function:  constructor * Description: Создает 3 скрина для температуры, потенциометра и Vdda ******************************************************************************/ cScreenManager::cScreenManager(cLcdDriver *pLcdDriver, const cVariablesDirector *pVariablesDirector) {   ASSERT(pLcdDriver != NULL);   ASSERT(pVariablesDirector != NULL);     this->pScreen[TEMPERATURE_SCREEN_ID] =              (iScreen*)(new cScreenTemperature(pLcdDriver, pVariablesDirector));   this->pScreen[TRIMMER_SCREEN_ID] =              (iScreen*)(new cScreenTrimmer(pLcdDriver, pVariablesDirector));   this->pScreen[VDDA_SCREEN_ID] =              (iScreen*)(new cScreenVdda(pLcdDriver, pVariablesDirector));   this->screenId = TEMPERATURE_SCREEN_ID;   this->pCurrentScreen = this->pScreen[this->screenId];   this->eMode = SM_single; } /******************************************************************************* * Function:  show * Description: Показывает текущий скрин, и  в зависимости от режима выбираем следующий для показа скрин  ******************************************************************************/ void cScreenManager::show(void)   {   switch (eMode)   {     case SM_single:       this->pCurrentScreen->show();     break;     case SM_sequence:       this->pCurrentScreen->show();       this->nextScreen();     break;     default:     break;   } }   /******************************************************************************* * Function:  nextScreen * Description: перемещаемся на следующий срин ******************************************************************************/ void cScreenManager::nextScreen(void)   {   this->screenId ++;   if (this->screenId >= SCREEN_NUM)   {     this->screenId = TEMPERATURE_SCREEN_ID;       }   this->pCurrentScreen = this->pScreen[this->screenId];   } /******************************************************************************* * Function:  nextMode * Description: устанавливаем следующий режим показа скринов ******************************************************************************/ void cScreenManager::nextMode(void)   {   if (this->eMode  == SM_single)   {     this->eMode = SM_sequence;   }   else   {     this->eMode = SM_single;   }     } 

Ну что же остался последний штрих — сделать активный объект для периодического вывода инфы на индикатор:
image

А реализация вообще проста и понятна:

lcddirector.h

#include "iactiveobject.h"      //lint !e537 Для интерфейса iActiveObject   #include "lcddriver.h"          //lint !e537 Для cLcdDriver #include "screenmanager.h"      //lint !e537 Для cScreenManager  #include "variablesdirector.h"  //lint !e537 Для pVariableDirector    class cLcdDirector : public iActiveObject {   public:     explicit cLcdDirector(const cVariablesDirector *pVariableDirector);     virtual void run(void);     private:     cLcdDriver* pLcdDriver;     cScreenManager *pScreenManager;     }; 

#include "lcddirector.h"      // Определение класса #include "susuassert.h"       // Для ASSERT #include "types.h"            // Стандартные типы проекта #include "buttonscontroller.h" // Для tButton #include <limits.h>           //  Для ULONG_MAX #define LCD_DELAY (tU32) (1500/portTICK_PERIOD_MS) /******************************************************************************* * Function:  constructor * Description: Создает экземпляр класса cLcdDriver и передает его в создаваемый *              экзепляр класса cScreenManager, для вывода изображения на Lcd ******************************************************************************/ cLcdDirector::cLcdDirector(const cVariablesDirector *pVariablesDirector)   {   ASSERT(pVariablesDirector != NULL);   this->pLcdDriver =  new cLcdDriver();    this->pScreenManager =  new cScreenManager(this->pLcdDriver,                                              pVariablesDirector); } /******************************************************************************* * Function:  run * Description: Задача управления выводом на идикатор. Ждет нотификацию от кнопок *              по первой кнопке меняем скрины, по второй режим вывода. ******************************************************************************/ void cLcdDirector::run(void) {   tU32 button = (tU32) 0;   tBoolean status = FALSE;   tButtons eButton = BT_none;       for(;;)   {     status = (tBoolean)oRTOS.taskNotifyWait((tU32)0, (tU32)ULONG_MAX, &button, LCD_DELAY);     if (status == TRUE)   //lint !e731 Сравниваем чтобы было понятнее     {       eButton = (tButtons)button;       switch (eButton)       {         case BT_button1:         this->pScreenManager->nextScreen();          break;         case BT_button2:         this->pScreenManager->nextMode();          break;         case BT_none:         break;         default:         break;       }     }      this->pScreenManager->show();       }  } 

И вот он результат 4 недельного разбирательства с АРМ контроллером

По окончании почистил проект линтом, он много чего обнаружил, и вот пример найденной ошибки:
Info 750: local macro ‘Symbol_26’ (line 65, file AHardware\Lcd\lcddriver.cpp) not referenced
Info 750: local macro ‘Symbol_3E’ (line 89, file AHardware\Lcd\lcddriver.cpp) not referenced

А ведь и правда, вот в этом массиве массиве файла lcddriver.cpp, сработал копипаст и два символа просто пропали, возможно это было бы обнаружено во время тестирования, а возможно и нет, но линт хорошая штука.
//таблица конвертации (ASCI кода буквы — ASCI код ‘ ‘(пробела)) в значение на сегментах индикатора
const tU32 cLcdDriver::charToLcdSymbol[] =
{
Symbol_20,
Symbol_21, Symbol_22, Symbol_23, Symbol_24, Symbol_25,
Symbol_27, Symbol_27, Symbol_28, Symbol_29, Symbol_2A,
Symbol_2B, Symbol_2C, Symbol_2D, Symbol_2E, Symbol_2F,
Digit_0, Digit_1, Digit_2, Digit_3, Digit_4,
Digit_5, Digit_6, Digit_7, Digit_8, Digit_9,
Symbol_3A, Symbol_3B, Symbol_3C, Symbol_3D,
Symbol_3D, Symbol_3F, Symbol_40, Symbol_A,
Symbol_B, Symbol_C, Symbol_D, Symbol_E, Symbol_F,
Symbol_G, Symbol_H, Symbol_I, Symbol_J, Symbol_K,
Symbol_L, Symbol_M, Symbol_N, Symbol_O, Symbol_P,
Symbol_Q, Symbol_R, Symbol_S, Symbol_T, Symbol_U,
Symbol_V, Symbol_W, Symbol_X, Symbol_Y, Symbol_Z
};

Ну что же на этом пока все. Реализованы все поставленные (мною для меня же) требования, использована ОСРВ, без единого семафора и критической секции (за исключением синхронизации по нотификации никаких дополнительных «сложны вещей», остальные данные берутся атомарно и не требуют блокировки).
Мое личное наблюдение и мнение — за 8 лет микропроцессоры шагнули вперед. Программирование все больше похоже на высокоуровневое, появилось много новых полезных блоков, и думаю, что студентам очень понравится 🙂

P.S.
По замечаниям prostoTyoma поменял архитектуру и реализацию фильтра. Рисовать не будут — хватит 🙂 но теперь фильтр выглядит так:

filter.h

#include "types.h"            //lint !e537 Стандартные типы проекта  class cFilter {   public:     explicit cFilter(const tF32 filterConst);     tF32 getFilteredValue() const { return filteredValue; };     void  updateFilteredValue(const tF32 nonFileredValue);   private:     tF32 filteredValue;     tF32 previousFilteredValue;     tF32 filterConstant; }; 

filter.cpp

#include "susuassert.h"       //lint !e537 Для ASSERT #include "types.h"            //lint !e537 Для типов проекта #include "filter.h"          //lint !e537 Описание класса /******************************************************************************* * Function:  constructor * Description: Инициализируем начальное значение фильтра нулем ******************************************************************************/ cFilter::cFilter(const tF32 filterConst)   {   ASSERT(filerConst != 0);   filteredValue = (tF32)0;   previousFilteredValue = (tF32)0;   this->filterConstant = filterConst; } /******************************************************************************* * Function:  constructor * Description: Обновляет фильтр новым значением ******************************************************************************/ void cFilter::updateFilteredValue(const tF32 nonFileredValue) {   this->filteredValue = this->filteredValue + (nonFileredValue -                          this->filteredValue) / this->filterConstant; } 

А вызов из переменной, например vdda, выглядит — вот так. Кстати тут сделал его не через указатель, что наверное и лучше, да и кода(текста) меньше и линт на указатели не ругается 🙂

******************************************************************************* * Function:  constructor * Description:  ******************************************************************************/ cVdda::cVdda(const cAdcDirector *pAdcDir) : iVariable(pAdcDir), oFilter(VDDA_FILTER_CONST)      { } /******************************************************************************* * Function:  calculate * Description: Расчет напряжения Vdda ******************************************************************************/ tF32 cVdda::calculate(void) {   //значение кода vdda   tF32 vdda = (tF32)0.0;      //коэффициенты калибровки температурного сенсора, см стр 289 CD00240193.pdf и   //стр 102 CD00277537.pdf   tF32 vddaCal = (tF32)(*((tU32 *)(VDDA_CAL_ADDR)) >> 16);   vdda = (tF32)this->pAdcDirector->channelValue[VDDA_CHANNEL];   //формула со см стр 289 CD00240193.pdf    vdda = VOLTS_3_0 * vdda / vddaCal;    this->oFilter.updateFilteredValue(vdda);   this->value = this->oFilter.getFilteredValue();    return  this->value;     } 

На этом все — думаю этого будет достаточно для курса, хотя у меня еще есть кое-какие идеи, например, UART остался не покрытым (реализовать, что-то простое типа FieldBus ха 🙂 Modbus, но все упирается во время. Если оно будет, продолжу развивать ближе к осени.
Да, ну сам окончательный, на данном этапе, проект то лежит тут:
Весь проект в IAR 6.50

ссылка на оригинал статьи http://habrahabr.ru/post/261837/


Комментарии

Добавить комментарий

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