Введение
В двух предыдущих частях 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. Для начала посмотрим как выглядит индикатор:
.
Как видно в нем 2 строки маленькая (верхняя) и БОЛЬШАЯ нижняя и еще куча всяких сегментов, которые мне нужны. Также я решил использовать только нижнюю БОЛЬШУЮ строку. Её будет достаточно, чтобы вывести значение переменных и единицы измерения. Ну вот и определились, с тем куда и что выводить, и после рисования получился класс:
Теперь настало время реализации. Для начала нужно было настроить кучу портов, а именно 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, ну и экран должен уметь рисовать сам себя. А теперь рисуем мы:
Самое время нарисовать наследников для вывода экранов Температуры, Vdda и Триммера. Они просто должны реализовывать один виртуальный метод show(), и потому все выглядит очень тривиально:
При реализации, выбирал метод преобразования 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 должен создавать все типы экранов (у нас их три cTemperatureScreen, cTrimmerScreen и сVddaScreen), но работать с ними должен через единый интерфейс, поэтому все созданные экраны будут храниться в массиве iScreen *pScreen[SCREEN_NUM];
Итак, снова рисуем для наглядности:
И неотходя от кассы реализуем:
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;
}
}
Ну что же, остался последний штрих — сделать активный объект для периодического вывода инфы на индикатор:
А реализация вообще проста и понятна:
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: updateFilteredValue
* 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
******************************************************************************/
void cVdda::calculate(void)
{
//значение кода vdda
tF32 vdda = (tF32)0.0;
//коэффициент калибровки VDDA при 3.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();
}
На этом все. Думаю, этого будет достаточно для курса, хотя у меня еще есть кое-какие идеи, например, UART остался не покрытым (реализовать, что-то простое типа
Да, ну и сам окончательный, на данном этапе, проект лежит тут:
Весь проект в IAR 6.50