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

  • Tutorial

Введение


В двух предыдущих частях 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 лет микропроцессоры шагнули вперед. Программирование все больше похоже на высокоуровневое, появилось много новых полезных блоков, и думаю, что студентам очень понравится.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 0

Only users with full accounts can post comments. Log in, please.