blob: d98ed40cf3fc3f30a9f1668c13c22c8dac435041 [file] [log] [blame]
/***************************************************************************//**
* @file em_lcd.c
* @brief Liquid Crystal Display (LCD) Peripheral API
* @version 5.6.0
*******************************************************************************
* # License
* <b>Copyright 2017 Silicon Laboratories, Inc. www.silabs.com</b>
*******************************************************************************
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
* obligation to support this Software. Silicon Labs is providing the
* Software "AS IS", with no express or implied warranties of any kind,
* including, but not limited to, any implied warranties of merchantability
* or fitness for any particular purpose or warranties against infringement
* of any proprietary rights of a third party.
*
* Silicon Labs will not be liable for any consequential, incidental, or
* special damages, or any other relief, or for any claim by any third party,
* arising from your use of this Software.
*
******************************************************************************/
#include "em_lcd.h"
#if defined(LCD_COUNT) && (LCD_COUNT > 0)
#include "em_assert.h"
#include "em_bus.h"
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup LCD
* @brief Liquid Crystal Display (LCD) Peripheral API
* @details
* This module contains functions to control the LDC peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The LCD driver can drive up to 8x36 segmented
* LCD directly. The animation feature makes it possible to have active
* animations without the CPU intervention.
* @{
******************************************************************************/
/***************************************************************************//**
* @brief
* Initialize the Liquid Crystal Display (LCD) controller.
*
* @details
* Configures the LCD controller. You must enable
* it afterwards, potentially configuring Frame Control and interrupts first
* according to requirements.
*
* @param[in] lcdInit
* A pointer to the initialization structure which configures the LCD controller.
*
******************************************************************************/
void LCD_Init(const LCD_Init_TypeDef *lcdInit)
{
uint32_t dispCtrl = LCD->DISPCTRL;
EFM_ASSERT(lcdInit != (void *) 0);
/* Disable the controller before reconfiguration. */
LCD_Enable(false);
/* Make sure the other bit fields don't get affected (i.e., voltage boost). */
dispCtrl &= ~(0
#if defined(LCD_DISPCTRL_MUXE)
| _LCD_DISPCTRL_MUXE_MASK
#endif
| _LCD_DISPCTRL_MUX_MASK
| _LCD_DISPCTRL_BIAS_MASK
| _LCD_DISPCTRL_WAVE_MASK
#if defined(_LCD_DISPCTRL_VLCDSEL_MASK)
| _LCD_DISPCTRL_VLCDSEL_MASK
#endif
#if defined(_LCD_DISPCTRL_CONCONF_MASK)
| _LCD_DISPCTRL_CONCONF_MASK
#endif
#if defined(_LCD_DISPCTRL_MODE_MASK)
| _LCD_DISPCTRL_MODE_MASK
#endif
#if defined(_LCD_DISPCTRL_CHGRDST_MASK)
| _LCD_DISPCTRL_CHGRDST_MASK
#endif
);
/* Configure the controller according to the initialization structure. */
dispCtrl |= lcdInit->mux; /* Also configures MUXE. */
dispCtrl |= lcdInit->bias;
dispCtrl |= lcdInit->wave;
#if defined(_SILICON_LABS_32B_SERIES_0)
dispCtrl |= lcdInit->vlcd;
dispCtrl |= lcdInit->contrast;
#endif
#if defined(_SILICON_LABS_32B_SERIES_1)
dispCtrl |= lcdInit->mode;
dispCtrl |= (lcdInit->chargeRedistribution);
#endif
/* Update the display controller. */
LCD->DISPCTRL = dispCtrl;
#if defined(_SILICON_LABS_32B_SERIES_1)
LCD->FRAMERATE = lcdInit->frameRateDivider;
LCD_ContrastSet(lcdInit->contrastLevel);
#endif
/* Enable the controller if needed. */
if (lcdInit->enable) {
LCD_Enable(true);
}
}
#if defined(_SILICON_LABS_32B_SERIES_0)
/***************************************************************************//**
* @brief
* Select a source for VLCD.
*
* @param[in] vlcd
* Select a source for the VLCD voltage.
******************************************************************************/
void LCD_VLCDSelect(LCD_VLCDSel_TypeDef vlcd)
{
uint32_t dispctrl = LCD->DISPCTRL;
/* Select VEXT or VDD */
dispctrl &= ~_LCD_DISPCTRL_VLCDSEL_MASK;
switch (vlcd) {
case lcdVLCDSelVExtBoost:
dispctrl |= LCD_DISPCTRL_VLCDSEL_VEXTBOOST;
break;
case lcdVLCDSelVDD:
dispctrl |= LCD_DISPCTRL_VLCDSEL_VDD;
break;
default:
break;
}
LCD->DISPCTRL = dispctrl;
}
#endif
/***************************************************************************//**
* @brief
* Configure Update Control.
*
* @param[in] ud
* Configures the LCD update method.
******************************************************************************/
void LCD_UpdateCtrl(LCD_UpdateCtrl_TypeDef ud)
{
LCD->CTRL = (LCD->CTRL & ~_LCD_CTRL_UDCTRL_MASK) | ud;
}
/***************************************************************************//**
* @brief
* Initialize the LCD Frame Counter.
*
* @param[in] fcInit
* A pointer to the Frame Counter initialization structure.
******************************************************************************/
void LCD_FrameCountInit(const LCD_FrameCountInit_TypeDef *fcInit)
{
uint32_t bactrl = LCD->BACTRL;
EFM_ASSERT(fcInit != (void *) 0);
/* Verify that the FC Top Counter is within limits. */
EFM_ASSERT(fcInit->top < 64);
/* Reconfigure the frame count configuration. */
bactrl &= ~(_LCD_BACTRL_FCTOP_MASK
| _LCD_BACTRL_FCPRESC_MASK);
bactrl |= (fcInit->top << _LCD_BACTRL_FCTOP_SHIFT);
bactrl |= fcInit->prescale;
/* Set the Blink and Animation Control Register. */
LCD->BACTRL = bactrl;
LCD_FrameCountEnable(fcInit->enable);
}
/***************************************************************************//**
* @brief
* Configure the LCD controller Animation feature.
*
* @param[in] animInit
* A pointer to the LCD Animation initialization structure.
******************************************************************************/
void LCD_AnimInit(const LCD_AnimInit_TypeDef *animInit)
{
uint32_t bactrl = LCD->BACTRL;
EFM_ASSERT(animInit != (void *) 0);
/* Set Animation Register Values. */
LCD->AREGA = animInit->AReg;
LCD->AREGB = animInit->BReg;
/* Configure the Animation Shift and Logic. */
bactrl &= ~(_LCD_BACTRL_AREGASC_MASK
| _LCD_BACTRL_AREGBSC_MASK
| _LCD_BACTRL_ALOGSEL_MASK);
bactrl |= (animInit->AShift << _LCD_BACTRL_AREGASC_SHIFT);
bactrl |= (animInit->BShift << _LCD_BACTRL_AREGBSC_SHIFT);
bactrl |= animInit->animLogic;
#if defined(LCD_BACTRL_ALOC)
bactrl &= ~(_LCD_BACTRL_ALOC_MASK);
if (animInit->startSeg == 0) {
bactrl |= LCD_BACTRL_ALOC_SEG0TO7;
} else if (animInit->startSeg == 8) {
bactrl |= LCD_BACTRL_ALOC_SEG8TO15;
}
#endif
/* Reconfigure. */
LCD->BACTRL = bactrl;
/* Enable. */
LCD_AnimEnable(animInit->enable);
}
/***************************************************************************//**
* @brief
* Enables updating this range of LCD segment lines.
*
* @param[in] segmentRange
* A range of 4 LCD segment lines to enable or disable for all enabled COM
* lines.
*
* @param[in] enable
* Boolean true to enable segment updates, false to disable updates.
******************************************************************************/
#if defined(_SILICON_LABS_32B_SERIES_0)
void LCD_SegmentRangeEnable(LCD_SegmentRange_TypeDef segmentRange, bool enable)
{
if (enable) {
LCD->SEGEN |= segmentRange;
} else {
LCD->SEGEN &= ~((uint32_t)segmentRange);
}
}
#endif
/***************************************************************************//**
* @brief
* Turn on or clear a segment.
*
* @note
* For the Gecko Family, the maximum configuration is (COM-lines x Segment-Lines) 4x40.
* For the Tiny Gecko Family, the maximum configuration is 8x20 or 4x24.
* For the Giant Gecko Family, the maximum configuration is 8x36 or 4x40.
*
* @param[in] com
* A COM line to change.
*
* @param[in] bit
* A bit index indicating which field to change.
*
* @param[in] enable
* True will set segment, false will clear segment.
******************************************************************************/
void LCD_SegmentSet(int com, int bit, bool enable)
{
#if defined(_LCD_SEGD7L_MASK)
/* Tiny Gecko and Giant Gecko Families support up to 8 COM lines. */
EFM_ASSERT(com < 8);
#else
/* Gecko Family supports up to 4 COM lines. */
EFM_ASSERT(com < 4);
#endif
#if defined(_LCD_SEGD0H_MASK)
EFM_ASSERT(bit < 40);
#else
/* Tiny Gecko Family supports only "low" segment registers. */
EFM_ASSERT(bit < 32);
#endif
/* Use a bitband access for atomic bit set/clear of the segment. */
switch (com) {
case 0:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD0L), bit, enable);
}
#if defined(_LCD_SEGD0H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD0H), bit, enable);
}
#endif
break;
case 1:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD1L), bit, enable);
}
#if defined(_LCD_SEGD1H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD1H), bit, enable);
}
#endif
break;
case 2:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD2L), bit, enable);
}
#if defined(_LCD_SEGD2H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD2H), bit, enable);
}
#endif
break;
case 3:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD3L), bit, enable);
}
#if defined(_LCD_SEGD3H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD3H), bit, enable);
}
#endif
break;
#if defined(_LCD_SEGD4L_MASK)
case 4:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD4L), bit, enable);
}
#if defined(_LCD_SEGD4H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD4H), bit, enable);
}
#endif
break;
#endif
#if defined(_LCD_SEGD5L_MASK)
case 5:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD5L), bit, enable);
}
#if defined(_LCD_SEGD5H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD5H), bit, enable);
}
#endif
break;
#endif
case 6:
#if defined(_LCD_SEGD6L_MASK)
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD6L), bit, enable);
}
#if defined(_LCD_SEGD6H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD6H), bit, enable);
}
#endif
break;
#endif
#if defined(_LCD_SEGD7L_MASK)
case 7:
if (bit < 32) {
BUS_RegBitWrite(&(LCD->SEGD7L), bit, enable);
}
#if defined(_LCD_SEGD7H_MASK)
else {
bit -= 32;
BUS_RegBitWrite(&(LCD->SEGD7H), bit, enable);
}
#endif
break;
#endif
default:
EFM_ASSERT(0);
break;
}
}
/***************************************************************************//**
* @brief
* Update 0-31 lowest segments on a given COM-line in one operation
* according to the bit mask.
*
* @param[in] com
* Indicates a COM line to update.
*
* @param[in] mask
* A bit mask for segments 0-31.
*
* @param[in] bits
* A bit pattern for segments 0-31.
******************************************************************************/
void LCD_SegmentSetLow(int com, uint32_t mask, uint32_t bits)
{
uint32_t segData;
/* A maximum number of com lines. */
#if defined(_LCD_SEGD7L_MASK)
EFM_ASSERT(com < 8);
#else
/* Gecko Family supports up to 4 COM lines. */
EFM_ASSERT(com < 4);
#endif
switch (com) {
case 0:
segData = LCD->SEGD0L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD0L = segData;
break;
case 1:
segData = LCD->SEGD1L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD1L = segData;
break;
case 2:
segData = LCD->SEGD2L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD2L = segData;
break;
case 3:
segData = LCD->SEGD3L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD3L = segData;
break;
#if defined(_LCD_SEGD4L_MASK)
case 4:
segData = LCD->SEGD4L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD4L = segData;
break;
#endif
#if defined(_LCD_SEGD5L_MASK)
case 5:
segData = LCD->SEGD5L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD5L = segData;
break;
#endif
#if defined(_LCD_SEGD6L_MASK)
case 6:
segData = LCD->SEGD6L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD6L = segData;
break;
#endif
#if defined(_LCD_SEGD7L_MASK)
case 7:
segData = LCD->SEGD7L;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD7L = segData;
break;
#endif
default:
EFM_ASSERT(0);
break;
}
}
#if defined(_LCD_SEGD0H_MASK)
/***************************************************************************//**
* @brief
* Update the high (32-39) segments on a given COM-line in one operation.
*
* @param[in] com
* Indicates a COM line to update.
*
* @param[in] mask
* A bit mask for segments 32-39.
*
* @param[in] bits
* A bit pattern for segments 32-39.
******************************************************************************/
void LCD_SegmentSetHigh(int com, uint32_t mask, uint32_t bits)
{
uint32_t segData;
#if defined(_LCD_SEGD7H_MASK)
EFM_ASSERT(com < 8);
#else
EFM_ASSERT(com < 4);
#endif
/* A maximum number of com lines. */
switch (com) {
case 0:
segData = LCD->SEGD0H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD0H = segData;
break;
case 1:
segData = LCD->SEGD1H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD1H = segData;
break;
case 2:
segData = LCD->SEGD2H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD2H = segData;
break;
case 3:
segData = LCD->SEGD3H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD3H = segData;
break;
#if defined(_LCD_SEGD4H_MASK)
case 4:
segData = LCD->SEGD4H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD4H = segData;
break;
#endif
#if defined(_LCD_SEGD5H_MASK)
case 5:
segData = LCD->SEGD5H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD5H = segData;
break;
#endif
#if defined(_LCD_SEGD6H_MASK)
case 6:
segData = LCD->SEGD6H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD6H = segData;
break;
#endif
#if defined(_LCD_SEGD7H_MASK)
case 7:
segData = LCD->SEGD7H;
segData &= ~(mask);
segData |= (mask & bits);
LCD->SEGD7H = segData;
break;
#endif
default:
break;
}
}
#endif
#if defined(_SILICON_LABS_32B_SERIES_0)
/***************************************************************************//**
* @brief
* Configure the contrast level on the LCD panel.
*
* @param[in] level
* The contrast level in range 0-31.
******************************************************************************/
void LCD_ContrastSet(int level)
{
EFM_ASSERT(level < 32);
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_CONLEV_MASK)
| (level << _LCD_DISPCTRL_CONLEV_SHIFT);
}
#endif
#if defined(_SILICON_LABS_32B_SERIES_1)
/***************************************************************************//**
* @brief
* Configure the contrast level on the LCD panel.
*
* @param[in] level
* The contrast level in range 0-63.
******************************************************************************/
void LCD_ContrastSet(int level)
{
EFM_ASSERT(level < 64);
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_CONTRAST_MASK)
| (level << _LCD_DISPCTRL_CONTRAST_SHIFT);
}
#endif
/***************************************************************************//**
* @brief
* Configure the bias level on the LCD panel.
*
* @param[in] bias
* The bias level.
******************************************************************************/
void LCD_BiasSet(LCD_Bias_TypeDef bias)
{
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_BIAS_MASK) | bias;
}
#if defined(_SILICON_LABS_32B_SERIES_0)
/***************************************************************************//**
* @brief
* Configure voltage booster
*
* The resulting voltage level is described in each part number's data sheet
*
* @param[in] vboost
* Voltage boost level
******************************************************************************/
void LCD_VBoostSet(LCD_VBoostLevel_TypeDef vboost)
{
/* Reconfigure Voltage Boost */
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_VBLEV_MASK) | vboost;
}
#endif
#if defined(LCD_CTRL_DSC)
/***************************************************************************//**
* @brief
* Configure the bias level for a specific segment line for Direct Segment Control.
*
* @note
* When DSC is active, each configuration takes up 4 bits in the Segment
* Registers (SEGD0L/SEGD1H) which defines the bias level.
* For optimal use of this feature, the entire SEGD-registers should be set
* at once in an optimized routine. Therefore, this function
* shows how to correctly configure the bias levels and should be used
* with care.
*
* @param[in] segmentLine
* A segment line number.
*
* @param[in] biasLevel
* The bias configuration level, 0-4. This value must be within the constraints
* defined by the LCD_DISPCTRL bias settings. For more information,
* see the applicable Reference Manual and data sheet.
******************************************************************************/
void LCD_BiasSegmentSet(int segmentLine, int biasLevel)
{
int biasRegister;
int bitShift;
volatile uint32_t *segmentRegister;
#if !defined(_LCD_SEGD0H_MASK)
EFM_ASSERT(segmentLine < 20);
/* A bias configuration for 8 segment lines per SEGDnL register. */
biasRegister = segmentLine / 8;
bitShift = (segmentLine % 8) * 4;
switch (biasRegister) {
case 0:
segmentRegister = &LCD->SEGD0L;
break;
case 1:
segmentRegister = &LCD->SEGD1L;
break;
case 2:
segmentRegister = &LCD->SEGD2L;
break;
case 3:
segmentRegister = &LCD->SEGD3L;
break;
default:
segmentRegister = (uint32_t *)0x00000000;
EFM_ASSERT(0);
break;
}
#else
EFM_ASSERT(segmentLine < 40);
/* A bias configuration for 10 segment lines per SEGDn L+H registers. */
biasRegister = segmentLine / 10;
bitShift = (segmentLine % 10) * 4;
switch (biasRegister) {
case 0:
if (bitShift < 32) {
segmentRegister = &LCD->SEGD0L;
} else {
segmentRegister = &LCD->SEGD0H;
bitShift -= 32;
}
break;
case 1:
if (bitShift < 32) {
segmentRegister = &LCD->SEGD1L;
} else {
segmentRegister = &LCD->SEGD1H;
bitShift -= 32;
}
break;
case 2:
if (bitShift < 32) {
segmentRegister = &LCD->SEGD2L;
} else {
segmentRegister = &LCD->SEGD1H;
bitShift -= 32;
}
break;
case 3:
if (bitShift < 32) {
segmentRegister = &LCD->SEGD3L;
} else {
segmentRegister = &LCD->SEGD3H;
bitShift -= 32;
}
break;
default:
segmentRegister = (uint32_t *)0x00000000;
EFM_ASSERT(0);
break;
}
#endif
/* Configure a new bias setting. */
*segmentRegister = (*segmentRegister & ~(0xF << bitShift)) | (biasLevel << bitShift);
}
#endif
#if defined(LCD_CTRL_DSC)
/***************************************************************************//**
* @brief
* Configure the bias level for a specific segment line.
*
* @note
* When DSC is active, each configuration takes up 4 bits in the Segment
* Registers (SEGD4L/SEGD4H) which defines bias level.
* For optimal use of this feature, the entire SEGD-registers should be set
* at once in a optimized routine. Therefore, this function
* shows how to correctly configure the bias levels and should be used
* with care.
*
* @param[in] comLine
* A COM line number, 0-7.
*
* @param[in] biasLevel
* The bias configuration level, 0-4. This value must be within the constraints
* defined by the LCD_DISPCTRL bias settings.
* For more information, see the appropriate Reference Manual and data sheet.
******************************************************************************/
void LCD_BiasComSet(int comLine, int biasLevel)
{
int bitShift;
EFM_ASSERT(comLine < 8);
bitShift = comLine * 4;
LCD->SEGD4L = (LCD->SEGD4L & ~(0xF << bitShift)) | (biasLevel << bitShift);
}
#endif
#if defined(_SILICON_LABS_32B_SERIES_1)
/***************************************************************************//**
* @brief
* Configure the mode for the LCD panel.
*
* @param[in] mode
* A mode.
******************************************************************************/
void LCD_ModeSet(LCD_Mode_Typedef mode)
{
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_MODE_MASK) | mode;
}
/***************************************************************************//**
* @brief
* Configure the charge redistribution cycles for the LCD panel.
*
* @param[in] chgrDst
* Charge redistribution cycles, range 0-4.
******************************************************************************/
void LCD_ChargeRedistributionCyclesSet(uint8_t cycles)
{
EFM_ASSERT(cycles <= 4);
LCD->DISPCTRL = (LCD->DISPCTRL & ~_LCD_DISPCTRL_CHGRDST_MASK)
| (cycles << _LCD_DISPCTRL_CHGRDST_SHIFT);
}
#endif
/** @} (end addtogroup LCD) */
/** @} (end addtogroup emlib) */
#endif /* defined(LCD_COUNT) && (LCD_COUNT > 0) */