Май 232012
 

Логическим продолжением описания работы модулей ADC и контроллера LCD было бы их совместное использование для вывода на экран результата преобразования. В предыдущих примерах , где рассматривалась работа с контроллером LCD, были использованы два варианта настройки этого модуля:

   

  • Инициализация через прямую установку нужных значений в регистрах LCD. Функции из библиотек не использовались, подключался только один внешний файл stm32l1xx.h, поскольку в нем заданы готовые битовые маски для регистров периферии и символические имена этих регистров, привязанные к конкретным адресам в пространстве памяти. Вот ссылка на статью STM32L-DISCOVERY. Подключаем LCD. Здесь же дано описание принципа работы встроенного контроллера LCD, а также дисплея, размещенного на плате.
  • В другом примере инициализация портов и контроллера LCD также производилась напрямую через установку/сброс бит в регистрах (STM32L-DISCOVERY. Стандартные библиотеки для LCD.). Для вывода на экран строк и отдельных символов использовались готовые файлы библиотек StdPeriph_lib и демонстрационного проекта для STM32L-DISCOVERY. Некоторые библиотечные файлы использовались не целиком, а в “урезанных” вариантах, сохранив только нужные в данном случае функции.

В статье о работе с ADC библиотеки также не использовались (STM32L. ADC — Аналого-цифровой преобразователь.), настройки делались напрямую через регистры (STM32L. Регистры АЦП.).

В данном примере будет рассмотрена настройка ADC, LCD, GPIO и системы тактирования RCC только через функции стандартных библиотек Std_Periph_Library и библиотек из демонстрационного проекта от STMicroelectronics для платы STM32L-DISCOVERY.

Вывод PA5 задействован в качестве входа ADC, результат измерения выводится на дисплей платы. Проект создан в среде IAR EWARM, к нему подключены следующие файлы:

  • startup_stm32l1xx_md.s и system_stm32l1xx.c. Эти 2 файла стандартно подключаются к любому проекту в IAR.
  • stm32l1xx_adc.c – функции инициализации ADC
  • stm32l1xx_gpio.c – функции инициализации портов ввода/вывода
  • stm32l1xx_lcd.c – функции инициализации контроллера LCD
  • stm32l1xx_rcc – функции включения и выбора источников тактирования, а также разрешения тактирования нужной периферии
  • stm32l1xx_pwr.c – здесь используется только функция, разрешающая доступ к домену RTC (real time clock – часы реального времени). В регистрах RTC выбирается источник тактирования, который является общим для RTC и контроллера LCD.
  • stm32l_discovery_lcd.c – это уже файл из демонстрационного проекта для платы STM32L-DISCOVERY. В его “теле” подключаются еще несколько файлов (например, discover_board.h и др.). Функции этого файла позволяют писать в программе текст, для вывода на дисплей, в виде готовой строки (типа “Hello World”) или отдельных символов. Далее, в этих файлах, символы преобразуются в числовые значения, записываемые в ячейки памяти LCD. Все эти значения вычисляются с привязкой к схеме подключения выводов микроконтроллера и дисплея платы. Эти файлы “заточены” именно под данную плату, для дисплея другого типа нужно будет создавать свои функции преобразования символов.

Файлы из демо-проекта для STM32L-DISCOVERY собраны в отдельный каталог Utilities, в опциях проекта IAR в секции C/C++ Compiler необходимо прописать путь к этой папке. Сюда из демо-проекта взяты следующие файлы:

  • discover_board.h
  • stm32l_discovery_lcd.h
  • stm32l_discovery_lcd.c
  • discover_functions.h

Это то, что было минимально необходимо для работы создаваемого проекта. При желании, эти файлы можно скорректировать, оставив в них только то, что нужно под конкретный проект. Или собрать из этих файлов один, поместив туда только нужные функции. Так, например, я не стал добавлять в проект файл discover_functions.c, взяв из него только функцию преобразования convert_into_char(), и поместив ее в текст основной программы main.c. Сделал так, потому что в этом файле подключаются еще некоторые другие, пришлось бы и их добавлять в проект. А также в нем есть ненужные именно в данном проекте функции, например, для работы с сенсорным полем платы.

Далее приведен текст программы

#include "stm32l_discovery_lcd.h"

//Объявляем структуры
GPIO_InitTypeDef    GPIO_InitStructure;
LCD_InitTypeDef     LCD_InitStruct;
ADC_InitTypeDef     ADC_InitStruct;

uint16_t ADC_Data; //Переменная для хранения результата преобразования ADC
uint16_t strDisp[6]; //Массив символов для вывода на дисплей

//Объявляем функцию, которая конвертирует результат преобразования ADC в массив символов ASCII 
void convert_into_char(uint32_t number, uint16_t *p_tab);

int main()
{
  //Выбор источника тактирования SYSCLK
  RCC_HSICmd(ENABLE); //Включаем внутренний генератор HSI - 16 МГц
  while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET); //Ждем стабилизации HSI
  RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); //Выбираем HSI в качестве источника SYSCLK
  
  //Разрешаем тактирование портов A, B, C  
  RCC_AHBPeriphClockCmd((RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOC), ENABLE);
  
  //Инициализация выводов порта A для работы LCD
  //Задаем в структуре нужные выводы порта для управления LCD
  GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 
                                 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 
                                   | GPIO_Pin_15);
  /*Эти выводы конфигурируем на работу с альтернативной функцией.
  С какой именно задаем ниже*/
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  //Инициализируем порт A с помощью созданной структуры
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  /*Отдельно для каждого вывода выбираем альтернативную функцию работы с LCD.
  GPIO_PinSource1..15 можно заменить GPIO_Pin_1..15 - это равнозначно*/  
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_LCD);
  
  //Инициализация вывода PA5 порта А для работы с ADC
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //Вывод порта для работы с ADC
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //Аналоговый режим для вывода PA5
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //Отключаем подтягивающие резисторы
  GPIO_Init(GPIOA, &GPIO_InitStructure); //Конфигурируем вывод 
  
  /*Инициализация выводов порта B для работы с LCD. Последовательность та же, 
  только выводы другие*/
  GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 
                                 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 
                                   | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 
                                     | GPIO_Pin_14 | GPIO_Pin_15);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_LCD);
  
  //Инициализация выводов порта C для работы с LCD
  GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 
                                 | GPIO_Pin_3 | GPIO_Pin_6 |GPIO_Pin_7 
                                   | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 
                                     | GPIO_Pin_11);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource0, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_LCD);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_LCD);
  
  //Инициализация ADC
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //Включаем тактирование ADC
  ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;  //Разрешение ADC - 12 бит
  ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;  //Выравнивание результата вправо
  ADC_Init(ADC1, &ADC_InitStruct);  //Конифигурируем модуль ADC заданной структурой
  /*Далее делаем следующие настройки для регулярного канала: 
  Выбор ADC - ADC1,
  канал № 5,
  число преобразований в последовательности - 1, 
  sample time - 16 тактов*/
  ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_16Cycles);
  ADC_Cmd(ADC1, ENABLE); //И включаем ADC
  while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADONS) == RESET); //Ждем готовности ADC
  
  //Задаем источник тактирования для LCD
  //Разрешаем тактирование модулей LCD и PWR
  RCC_APB1PeriphClockCmd((RCC_APB1Periph_PWR | RCC_APB1Periph_LCD), ENABLE);
  //Разрешаем доступ к регистрам RTC
  PWR_RTCAccessCmd(ENABLE);
  //Включаем внешний генератор LSE - 32,768 кГц 
  RCC_LSEConfig(RCC_LSE_ON);
  //Ждем его готовности
  while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
  //Включаем тактирование модуля RTC
  RCC_RTCCLKCmd(ENABLE);
  //Выбираем LSE в качестве источника тактирования RTC (соответственно и LCD)
  RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    
  //Инициализация LCD
  LCD_InitStruct.LCD_Prescaler = LCD_Prescaler_16; //CLKPS = LCDCLK/16
  LCD_InitStruct.LCD_Divider = LCD_Divider_17; //LCD frequency = CLKPS/17
  LCD_InitStruct.LCD_Duty = LCD_Duty_1_4; //1/4 duty
  LCD_InitStruct.LCD_Bias = LCD_Bias_1_3; //1/3 Bias
  //В качестве источника питания для LCD выбираем внутренний step-up конвертер
  LCD_InitStruct.LCD_VoltageSource = LCD_VoltageSource_Internal; 
  LCD_Init(&LCD_InitStruct); //Конфигурируем LCD
  
  LCD_MuxSegmentCmd(ENABLE); //Делаем "ремаппинг" выводов
  LCD_ContrastConfig(LCD_Contrast_Level_0); //Задана минимальная контрастность
  LCD_WaitForSynchro(); //Ждем синхронизации регистра LCD_FCR
  LCD_Cmd(ENABLE); //разрешаем работу контроллера LCD
  //Ждем готовности step-up конвертера и контроллера LCD
  LCD_GetFlagStatus(LCD_FLAG_RDY | LCD_FLAG_ENS); 
  /*На этом инициализация LCD закончена. Для вывода символов будем использовать
    функцию LCD_GLASS_DisplayStrDeci() из демо-проекта*/
  while(1)
  {
    ADC_SoftwareStartConv(ADC1); //Запускаем преобразование
    ADC_Data = ADC_GetConversionValue(ADC1); //Считываем результат преобразования
    ADC_Data = ADC_Data*3000/4096; //Масштабируем. Величина 3 Волта выбрана приблизительно
    /*Конвертируем результат преобразования ADC в массив символов.
    Результат сохраняется в элементах массива 1..4*/
    convert_into_char(ADC_Data, strDisp); 
    strDisp[0] = ' '; //Первое знакоместо дисплея оставляем пустым
    strDisp[4] = ' '; //Четвертое знакоместо дисплея тоже стираем. Здесь были единицы милливольт
    strDisp[5] = 'V'; //В последнее знакоместо дисплея выводим букву V
    strDisp[1] |= DOT; //Десятичная точка
    /*Следующая функция выводит на экран массив strDisp. Еще она позволяет выводить на 
    экран десятичную точку или двоеточие  в любой позиции экрана. 
    В данном случае команда strDisp[1] |= DOT выводит точку после первой цифры*/ 
    LCD_GLASS_DisplayStrDeci(strDisp);
  }
}

//Эта функция конвертирует результат преобразования ADC в массив символов
void convert_into_char(uint32_t number, uint16_t *p_tab)
{
  uint16_t units=0, tens=0, hundreds=0, thousands=0, misc=0;
  
  units = (((number%10000)%1000)%100)%10;
  tens = ((((number-units)/10)%1000)%100)%10;
  hundreds = (((number-tens-units)/100))%100%10;
  thousands = ((number-hundreds-tens-units)/1000)%10;
  misc = ((number-thousands-hundreds-tens-units)/10000);
  
  *(p_tab+4) = units + 0x30;
  *(p_tab+3) = tens + 0x30;
  *(p_tab+2) = hundreds + 0x30;
  *(p_tab+1) = thousands + 0x30;
  *(p_tab) = misc + 0x30;
}

Текст снабжен комментариями, если что непонятно, спрашивайте. Для настройки LCD возможны 2 варианта:

  1. Использовать готовую функцию инициализации из файла stm32l_discovery_lcd.c, в которой готовые параметры уже заданы
  2. Задать самому параметры настройки в функции инициализации из библиотечного файла stm32l1xx_lcd.c

В примере использован второй вариант.

Архив с файлами проекта можно загрузить по ссылке Проект вывода на дисплей результата преобразования АЦП. Проект создан в IAR EWARM версии 6.30.

Другие статьи:

  8 Responses to “STM32L-DYSCOVERY. АЦ-преобразование и вывод на дисплей.”

  1. Спасибо, большое!! Пробую разбираться и с этим проектом…

  2. друг, почему у тебя так? :

    //Ждем готовности step-up конвертера и контроллера LCD
    LCD_GetFlagStatus(LCD_FLAG_RDY | LCD_FLAG_ENS);

    ты же ничего не ждёшь.
    а в демке вот так:

    /* Wait Until the LCD is enabled */
    while(LCD_GetFlagStatus(LCD_FLAG_ENS) == RESET)
    {
    }

    /*!< Wait Until the LCD Booster is ready */
    while(LCD_GetFlagStatus(LCD_FLAG_RDY) == RESET)
    {
    }

    Так вот, я почему спрашиваю.
    Я из демки пример передрал в свой проект, почти один в один.
    Однако у меня зависает на команде
    while(LCD_GetFlagStatus(LCD_FLAG_RDY) == RESET)

    Почему — никак не могу понять…
    А ты почему-то вообще даже не ждёшь :)

    • Спасибо за подсказку, действительно состояние флагов у меня проверяется, но нигде далее это не используется. Это неправильно, подумаю как изменить программу. Возможно эту строку я тоже взял из примера, сейчас уже не помню.

  3. PANYTA

    Здравствуйте.
    Возможно не в той теме задаю вопрос, но тут как пример.

    Разъясните, пожалуйста поподробнее про подключение файлов к проекту. Или дайте ссылку на эту информацию.

    Какая разница между #include в тексте программы (в main) и ADD в менеджере проекта (Workspace) ?
    stm32l1xx_adc.c к примеру подключен в Workspace, но нету его «инклюда» в тексте main.
    И каким из них надо прописывать пути в препроцессоре ? Или ко всем…

    • Пути надо указывать ко всем тем файлам, которые не находятся в собственных каталогах среды разработки, например IAR. Как в случае с библиотеками stdperiph_lib, в частности, чтобы компилятор знал, где искать эти файлы.
      Другой вопрос, на самом деле, очень непростой. Как видите, в тексте программы подключаются только файлы с расширением .h, то есть «хидеры». Я стараюсь не отвечать на вопросы, связанные с языком Си или особенностями среды разработки, если дело не касается простейших вещей, поскольку сам не являюсь крутым знатоком в этих областях. Поэтому могу только отослать к учебникам по Си и документации по IDE (она есть на английском языке).

  4. PANYTA

    Даа… Ужжж… :)

    Если уж вы не «крутой знаток», то я ващще со знаком минус.

    Спасибо за ответы.

  5. Всем привет. Перенёс этот код в Keil (попытался по крайней мере), он мне выдаёт ошибку:
    .\LCD2.axf: Error: L6218E: Undefined symbol Delay (referred from stm32l_discovery_lcd.o).
    Не пойму как её исправить. Помогите пожалуйста!

  6. Народ можете пожалуйста ссылочку на примеры настройки регистров для работы с LCD на STM32f103vc, заранее благодарен.