back to
www.audiodesignguide.com

To get more information contact me at: webmaster@audiodesignguide.com


DAC and Volume control with LCD and Touch Screen
April 2024

INTRODUCTION

All the modern DAC, ADC and Digital Volume chips need to be configurated and managed using a microcontroller connected using I2C or SPI.
Here I would like to give you the elements to create this control module without software or hardware problems and with no expensive components.
Before starting I have to give a special thanks to the STM community because they have helped me several times to solve problems and I reciprocated by adding useful content .

 

STM32  MODULES

Many years ago I started developing DAC and Volume controls using 8 bit PIC microcontroller by MIcrochip but in the last years a new line of microcontroller by STM32 give more performances, integrated evaluation module with lcd and a free develop environment.
Below I insert a series of modules that I used to create prototypes and products in production for some companies.
For all has been used the
USB ST-LINK/V2 in-circuit debugger and programmer in some case included in the module.

STM32F746G-DISCO

- STM32F746NGH6 Arm® Cortex® core-based microcontroller frequency of up to 216 MHz with 1 Mbyte of Flash memory and 340 Kbytes of RAM
- 4.3” RGB 480×272 color LCD-TFT
- Capacitive touch screen
- Ethernet compliant with IEEE-802.3-2002
- USB OTG HS
- USB OTG FS
- SAI audio codec
- Two ST-MEMS digital microphones
- 128-Mbit Quad-SPI Flash memory
- 128-Mbit SDRAM (64 Mbits accessible)
- Two user and reset push-buttons
- MicroSD card
- On-board ST-LINK/V2

  schematic

STM32F469I-DISCOVERY

- STM32F469NIH6 Arm® 32-bit Cortex®-M4 microcontroller frequency of up to 180 MHz with 2 Mbytes of Flash memory and 324 Kbytes of RAM
- 4" inches 800x480 pixel
- LCD with MIPI DSI® interface
- Capacitive touch screen
- USB OTG FS 
- SAI Audio DAC
- 128-Mbit Quad-SPI NOR Flash
- 128-Mbit  SDRAM
- Two user and reset push-buttons
- MicroSD card
- On-board ST-LINK/V2


schematic
STM32F429BIT6 module + 5" or 7" LCD

- STM32F429BIT6 Arm® 32-bit Cortex®-M4 CPU frequency up to 180 MHz with 2 MB of Flash memory and Up to 256+4 KB of SRAM
- 5" or 7" LCD 800x480 connnected in RGB
- LCD parallel interface
- Capacitive touch screen
- 128-Mbit SDRAM
- Two user and reset push-buttons

- MicroSD card
- External ST-LINK/V2 is necessary to flash

Alixpress web shop







Click on STM32 module name to dowload the user manual.

Click on microprocessor name to download the datasheet of the chip. 
STM32F0DISCOVERY + 4x20 char LCD

- STM32F051R8T6 microcontroller featuring 64 KB Flash memory, 8 KB RAM frequency up to 48 MHz
- Two push buttons (user and reset) 

- On-board ST-LINK/V2

schematic
  ST-Link V2 Mini USB

Only the STM32F429BIT6 module need an external ST-Link V2 Mini USB to flash the code.

All these modules can be powered by USB during the develop phase and with an external power supply for the final use.

Here follows two photos to create the right connection from ST-LINK/V2 and the STM32F429BIT6 module (keep attention).

  

You can flash the STM32 microprocessor directly with the CubeIDE development platform Run button or with ST-LINK Utility.
In this case is necessary after the compiler steps is necessary produce the .hex file (see the last section of this page).


 

 

DEVELOPMENT PLATFORM

STM32CubeIDE is an advanced C/C++ development platform with peripheral configuration, code generation, code compilation, and debug features for STM32 microcontrollers and microprocessors.

After the selection of an empty STM32 MCU or MPU, or preconfigured microcontroller or microprocessor from the selection of a board or the selection of an example, the project is created and initialization code generated.
Support for ST-LINK (STMicroelectronics) and J-Link (SEGGER) debug probes.

If you are using a STM32 DISCOVERY module is possibile generate an empty project selecting the evaulation board.

There is also a function to generate the code for pin initialization in a visual mode but I don't like to use this because the automatically generated code is mixed with the manually entered code and if you don't follow the rules some changes could be lost. 

 

 

MICROPROCESSOR I/O PINS

The microprocessors have many I/O pins but there are many differences between one and the other.
Almost all can be configured as simple inputs or outputs to connect buttons or relays but only a specific set can be configured for SPI, I2C.
Also for the encoders the correct pair of pins must be identified to enable the specific function with timer.
So the first activity to define the connections is to identify the SPI, I2C, ADC and Timer (TIM) for encoder pins.
All of the presented STM32 modules allow access to allows access only to certain pins via dip connectors.
For the STM32F746-DISCO and the STM32F469-DISCOVERY you can buy the PCB Board per Arduino UNO R3 Shield Board to connect the pins.

  

Here the develop environment to test the STM32F429BIT6 module with 7" LCD.

 

 

 

I2C and SPI PROTOCOLS

The I2C and the SPI are serial communication protocols to allow communication between the microprocessor and other chips such as DAC, ADC and Volme Control.

There are many documents about these two protocols but this on eVision Systems is a good info.

 

I2C (Inter-Integrated Circuit) is an on-board communication protocol, which is ideal for short distances and low bandwidth.
It has a master-slave architecture in which all slaves are connected to the master via two wires: the serial data wire (SDA) and the serial clock wire (SCL). I2C is typically used for attaching lower-speed peripherals such as sensors to processors and microcontrollers over short distances within an integrated circuit.
The I2C protocol defines how to send data.  Initially, the master issues a start condition followed by the address of the slave device to which it is communicating.  Once the appropriate slave has identified its address, it looks for the proceeding read/write flag issued by the master.

  The Serial Peripheral Interface (SPI) is another serial communication protocol that is heavily  used in embedded systems.
Like I2C, it is also has a master-slave architecture but it is a 4+ wire bus.  
SPI requires a clock line (SCK), two data lines for transmitting data bidirectionally known as the MOSI and MISO lines. Additionally, there must be a slave select (SS or CS) line for each slave on the bus.  
Instead of utilizing an addressing system like I2C, multiple slaves are controlled by the master via the slave select lines.  

 

PERIPHERICALS

This microprocessors will be connected to varios periphericals:

The Infrared receiver should operate with 3.3VDC power supply, the same of STM32.

The relays will be connected to the STM32 through the use of a transistor to increase the output current of the microprocessor pins.

All the DAC, ADC and Volume control should operate with 3.3V SPI or I2C signals otherwise will be necessary use a level translator like the TXS0108E or the P82B96

An example of incompatible SPI signal is the MAS6116 Volume Control with 5V digital inputs and outputs.

Another example is the PGA2310 Volume Control with a 5V digital power supply but compatible digital inputs because the min High-Level Input Voltage is only 2V and it is not necessary use the PGA output signal.

 

MAS6116

 

DVCC is 5V so min Input high volage is 5 * 0.7 = 3.5V.

PGA2310

DVCC is 5V but min Input high volage is 2V so able to be connected to 3.3V STM32 SPI pins.


 

 

 

START WITH A STM32 DISCOVERY BOARD

As told before you can create an empty project with Cube IDE with few clicks

Select File - New - STM32 Project, on the following panel select Board Selector and search the model

 

 

Here select No

Now modify the main() with these lines of code to blick the blue led.

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */
    HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_RESET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

 

START WITH A SIMPLE CODE

Here a simple code to switch on the on-board LED with STM32F746-DISCO.
First identify a pin on STM32 module searching on user manual, D2 is a valid choice because have no timer, no I2C and no SPI.

D2 on ARDUINO connectors is connected to PG6 so port GPIOG and pin GPIO_PIN_6.

So connect a LED in series with a 680ohm resistor to this pin and use this code.

void main()
{
    HAL_Init();

    SystemClock_Config();

    GPIO_InitTypeDef gpio_init_structure;

     __HAL_RCC_GPIOG_CLK_ENABLE();                   // <--- enable port GPIOG

    gpio_init_structure.Pin   = GPIO_PIN_6;
    gpio_init_structure.Mode  = GPIO_MODE_OUTPUT_PP;
    gpio_init_structure.Pull  = GPIO_NOPULL;         // <--- no pull-up is necessary
    gpio_init_structure.Speed = GPIO_SPEED_LOW;
  
    HAL_GPIO_Init(GPIOG, &gpio_init_structure);      // <--- init port GPIOG

     HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_RESET); // <--- RESET mean low level so 0V

       HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_SET);   // <--- SET mean high level so 3.3V
}


 

 

 

 

 

 

 

ENCODER 

Here my code to use encoder with STM32 modules

First search on datasheet 2 pins with the same timer, for example TIM8_CH1 and TIM8_CH2.

In my case was a STM32F469 and I have used PC6 and PC7 pin associated to timer 8 (TIM8).

After search in the file stm32f4xx_hal_gpio_ex.h the relative AFx for your TIMx.

#define GPIO_AF3_TIM8 ((uint8_t)0x03) /* TIM8 Alternate Function mapping */

In the main.c insert the following code


TIM_HandleTypeDef htim8;

uint32_t TIM8_save_counter = 0;

void MX_TIM8_Init(void)
{
    TIM_Encoder_InitTypeDef sConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    GPIO_InitTypeDef gpio_init_structure;

    __HAL_RCC_GPIOC_CLK_ENABLE();

    __TIM8_CLK_ENABLE();

    gpio_init_structure.Pin = GPIO_PIN_6|GPIO_PIN_7;
    gpio_init_structure.Mode = GPIO_MODE_AF_PP;
    gpio_init_structure.Pull = GPIO_PULLUP;
    gpio_init_structure.Speed = GPIO_SPEED_LOW;
    gpio_init_structure.Alternate = GPIO_AF3_TIM8;

    HAL_GPIO_Init(GPIOC, &gpio_init_structure);

    htim8.Instance = TIM8;
    htim8.Init.Prescaler = 0;
    htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim8.Init.Period = 65535;
    htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim8.Init.RepetitionCounter = 0;
    htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

    sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
    sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
    sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
    sConfig.IC1Filter = 10;
    sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
    sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
    sConfig.IC2Filter = 0;

    if (HAL_TIM_Encoder_Init(&htim8, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
   
    if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
    //HAL_TIM_Encoder_Start(&htim8, TIM_CHANNEL_ALL);
    HAL_TIM_Encoder_Start(&htim8, TIM_CHANNEL_1|TIM_CHANNEL_2);
}


and in the main loop insert this code to check the rotation

if (TIM8->CNT != TIM8_save_counter)
{
    if (TIM8_save_counter == 0 && TIM8->CNT == 65535)
        buttonInUp();
    else if (TIM8_save_counter == 65535 && TIM8->CNT == 0)
        buttonInDown();
    else if (TIM8_save_counter < TIM8->CNT)
        buttonInDown();
    else
        buttonInUp();

    TIM8_save_counter = TIM8->CNT;
}

 

 

INFRARED

 

It is possible to use this very cheap infrared remote with the SC6122 chip.

This remote control utilizes NEC IR protocol for encoding.
In the following example has been used the pin PA15 (GPIOA - GPIO_PIN_15) and has been enabled the interrupt EXTI15_10_IRQn on the transition from a high level to a low level (GPIO_MODE_IT_FALLING).

#define IR_ADDR (0x20)
#define IR_ON   (0x1C)
#define IR_UP   (0x10)
#define IR_DOWN (0x1D)
#define IR_MUTE (0x11)
#define IR_FWR  (0x15)
#define IR_REW  (0x18)
#define IR_SET  (0x19)
#define IR_STD  (0x25)

if ((IR_addr1 == IR_ADDR && IR_addr2 == IR_ADDR) ||
   (IR_addr1 == IR2_ADDR1 && IR_addr2 == IR2_ADDR2))
{
    IR_addr1 = IR_addr2 = 0;

    switch(IR_data1)
    {
        case IR_UP:
        case IR2_UP:
            buttonUpClicked();
            break;

       case IR_DOWN:
       case IR2_DOWN:
           buttonDownClicked();
           break;

}

void EXTI15_10_IRQHandler(void)
{
#define MAX_NEG_START (10000)
#define MIN_NEG_START (8000)
#define MAX_POS_START (5000)
#define MIN_POS_START (3500)
#define MAX_POS_REP (2500)
#define MIN_POS_REP (1500)
#define MIN_POS_BIT1 (1200)
#define DELAY_STEP (250)

    int l, k;
    unsigned char data1, data2, addr1, addr2;


    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);

    for (k = 0; k < MAX_NEG_START && !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15); k += DELAY_STEP)
    {
       delayUs(DELAY_STEP);
    }

    if ((k < MAX_NEG_START) && (k > MIN_NEG_START))
    {
        for (l = 0; l < MAX_POS_START && HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15); l += DELAY_STEP)
        {
            delayUs(DELAY_STEP);
        }

        if (l < MAX_POS_REP && l > MIN_POS_REP)
        {
            while(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15));

            if (save_addr1 > 0)
            {
                IR_addr1 = save_addr1;
                IR_addr2 = save_addr2;
            }
            else
            {
                IR_addr1 = 0;
                IR_addr2 = 0;
            }
            return;
        }
        if ((l < MAX_POS_START) && (l > MIN_POS_START))
        {
            while (!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15));

            addr1 = IR_readByte(0, MIN_POS_BIT1);
            addr2 = IR_readByte(1, MIN_POS_BIT1);

            data1 = IR_readByte(0, MIN_POS_BIT1);
            data2 = IR_readByte(1, MIN_POS_BIT1);

#if OLD_REMOTE
            if (addr1 != addr2) return;
#endif
            if (data1 != data2) return;

            if (addr1 == 0) return;
            if (data1 == 0) return;

            save_addr1 = addr1;
            save_addr2 = addr2;

            IR_addr1 = addr1;
            IR_addr2 = addr2;
            IR_data1 = data1;
            IR_data2 = data2;
        }
    }
}

unsigned char IR_readByte(int inv, int CheckTime)
{
    unsigned char out, tmp[10];
    int i, k;

    for (k = 0; k < 8; k++)
    {
        for (i = 0; HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15); i += DELAY_STEP)
        {
            delayUs(DELAY_STEP);
        }
        while (!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15));

        if (i > CheckTime)
        {
            if (inv == 0 )
                tmp[k] = '1';
            else
                tmp[k] = '0';
        }
        else
        {
            if (inv == 0 )
                tmp[k] = '0';
            else
                tmp[k] = '1';
        }
    }
    out = 0;

    for (k = 7; k >= 0; k--)
    {
        if (tmp[k] == '1')
        {
            out <<= 1; out |= 1;
        }
        else
        {
            out <<= 1;
        }
    }
    return out;
}


void BSP_IR_Init()
{
    GPIO_InitTypeDef gpio_init_structure;

    __HAL_RCC_GPIOA_CLK_ENABLE();

    gpio_init_structure.Pin = GPIO_PIN_15;
    gpio_init_structure.Mode = GPIO_MODE_IT_FALLING;
    gpio_init_structure.Pull = GPIO_NOPULL;

    HAL_GPIO_Init(GPIOA, &gpio_init_structure);

    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

 

 

 

 

FRAM TO STORE CONFIGURATION

If you want to save some variables of configuration like volume level, balance and input selected also if the power is disconnected a solution is to add an external I2C FRAM chip.

I have tested the MB85RC256V availabe also in DIP module on Alixpress

Insert in the main.c these following lines to use the I2S functions:

extern void EEPROM_IO_Init(void);
extern HAL_StatusTypeDef EEPROM_IO_WriteData(uint16_t DevAddress, uint16_t MemAddress, uint8_t* pBuffer, uint32_t BufferSize);
extern HAL_StatusTypeDef EEPROM_IO_ReadData(uint16_t DevAddress, uint16_t MemAddress, uint8_t* pBuffer, uint32_t BufferSize);
extern HAL_StatusTypeDef EEPROM_IO_IsDeviceReady (uint16_t DevAddress, uint32_t Trials);

typedef struct saves_tag_conf {
    unsigned char not_first;
    unsigned char inputsel;
    int level;
    int balance;
} saved_conf_str;

saved_conf_str saved_conf; // = { 0, 0, 32, 0 };

Insert in the main() the init function:

EEPROM_IO_Init();

The byte 0b10100000 used as first parameter in the call to the functions is the I2C device address so in my case A0,A1 and A2 are connected to ground.   

void Read_Saved_Configuration()
{
     uint8_t ret;

     if ((ret = EEPROM_IO_IsDeviceReady(0b10100000, 10)) != 0x00)
     {
         return;
     }
     if ((ret = EEPROM_IO_ReadData(0b10100001, 0x1, (uint8_t *)&saved_conf, sizeof(saved_conf))) != 0x00)
     {
         return;
     }
     if (saved_conf.not_first != 0)
     {
         inputsel = saved_conf.inputsel;
         level    = saved_conf.level;
         balance  = saved_conf.balance;
     }
     saved_conf.not_first = 1;

     Write_Saved_Configuration();
}

void Write_Saved_Configuration()
{
    uint8_t ret;

    saved_conf.inputsel = inputsel;
    saved_conf.level = level;
    saved_conf.balance = balance;

    if ((ret = EEPROM_IO_IsDeviceReady(0b10100000, 10)) != 0x00)
    {
        return;
    }
    if ((ret = EEPROM_IO_WriteData(0b10100000, 0x1, (uint8_t *)&saved_conf, sizeof(saved_conf))) != 0x00)
    {
        return;
    }
}

 

 

 

LCD IMAGE CONVERTER

In order to display images on the graphic LCD modules is necessary transform this from standard file bmp,gig, jpeg and png to “C” source format for embedded applications.

Main

I am using this LCD Image Converter to configure in the following mode.

  1. load image

  2. configure parameters







    here download template file

  3. select convert

  4. select the output file

The output file is an include to add in the STM32CubeIDE project.

The images get a big amount of memory and this may be too large for the available flash space so a solution is store the images in an external Serial Flash memory (QSPI), in same case included in the STM32 module.

A single full screen image for a 800 x 480 pixel LCD get 750K bytes and this is the result of 800 x 480 x 2 / 1024 = 750.

 

 

 

QSPI TO STORE IMAGES

Some of STM32 discovery board have an integrated Quad SPI Flash Memory chip

32F746GDISCOVERY  N25Q128A                       128Mb   108 MHz   SPI-compatible serial bus interface
32F469IDISCOVERY    MT25QL128ABA1EW9    128Mb   166 MHz   SPI-compatible serial bus interface

It is possible store static images in this chip during the flash to have a larger space for the program.

Here the logo.h file generated by LCD converter from the logo.png image.

#ifndef logo_H_
#define logo_H_

#include <stdint.h>

// struct packing, pragma for GCC !!!
#pragma pack(push, 1)

typedef struct logo_tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} logo_BITMAPFILEHEADER; // size is 14 bytes

typedef struct logo_tagBITMAPINFOHEADER {
    uint32_t biSize;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    uint32_t biXPelsPerMeter;
    uint32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
} logo_BITMAPINFOHEADER; // size is 40 bytes

typedef struct logo_tag_Struct {
    // offset 0, size 14
    logo_BITMAPFILEHEADER fileHeader;
    // offset 14, size 40
    logo_BITMAPINFOHEADER infoHeader;
    // offset 54, size 51600 words
    uint16_t data[51600];
} logo_Struct;

const logo_Struct logo = {
{
    0x4d42u,
    sizeof(logo_BITMAPINFOHEADER) + sizeof(logo_BITMAPFILEHEADER) + (51600 * 2),
    0x0000u,
    0x0000u,
    sizeof(logo_BITMAPINFOHEADER) + sizeof(logo_BITMAPFILEHEADER)
},
{
    sizeof(logo_BITMAPINFOHEADER),
    400,
    129,
    1u,
    16,
    0x00000003u,
    (51600 * 2),
    0x00000000ul,
    0x00000000ul,
    0x00000000ul,
    0x00000000ul
},
{
    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, .....
}
};

// struct packing, pragma for GCC !!!
#pragma pack (pop)

#endif /* settings1_H_ */

 

Here the logo.h file modified by hand to use the QSPI

//#ifndef logo_H_
//#define logo_H_


#include <stdint.h>

// struct packing, pragma for GCC !!!
#pragma pack(push, 1)

typedef struct logo_tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} logo_BITMAPFILEHEADER; // size is 14 bytes

typedef struct logo_tagBITMAPINFOHEADER {
    uint32_t biSize;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    uint32_t biXPelsPerMeter;
    uint32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
} logo_BITMAPINFOHEADER; // size is 40 bytes

typedef struct logo_tag_Struct {
    // offset 0, size 14
    logo_BITMAPFILEHEADER fileHeader;
    // offset 14, size 40
    logo_BITMAPINFOHEADER infoHeader;
    // offset 54, size 51600 words
    uint16_t data[51600];
} logo_Struct;

#ifndef IMG_NO_DATA
#if defined ( __ICCARM__ )
#pragma location = ".textqspi"
#else
__attribute__((section(".textqspi")))
#endif


const logo_Struct logo = {
{
    0x4d42u,
    sizeof(logo_BITMAPINFOHEADER) + sizeof(logo_BITMAPFILEHEADER) + (51600 * 2),
    0x0000u,
    0x0000u,
    sizeof(logo_BITMAPINFOHEADER) + sizeof(logo_BITMAPFILEHEADER)
},
{
    sizeof(logo_BITMAPINFOHEADER),
    400,
    129,
    1u,
    16,
    0x00000003u,
    (51600 * 2),
    0x00000000ul,
    0x00000000ul,
    0x00000000ul,
    0x00000000ul
},
{
    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, .....
}
};

#else
extern const logo_Struct logo;
#endif


// struct packing, pragma for GCC !!!
#pragma pack (pop)

//#endif

 

create in the project a new main_image.c file with only these lines



#include "logo.h"

void dummy_init (void);

void dummy_init (void)
{
}

 

in your main.c use these lines to init the QSPI

 #define IMG_NO_DATA
#include "logo.h"


QSPI_HandleTypeDef hqspi;

hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 255;
hqspi.Init.FifoThreshold = 1;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE;
hqspi.Init.FlashSize = 1;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.FlashID = QSPI_FLASH_ID_1;
hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;

if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
    Error_Handler();
}

int status = BSP_QSPI_Init();

if (status == QSPI_NOT_SUPPORTED)
{
    BSP_LCD_DisplayStringAt(20, 10, (uint8_t*)"QSPI Initialization : FAILED.", LEFT_MODE);
}
else if (status == QSPI_ERROR)
{
    BSP_LCD_DisplayStringAt(20, 10, (uint8_t*)"QSPI Initialization : FAILED.", LEFT_MODE);
}
else
{
    BSP_LCD_DisplayStringAt(20, 10, (uint8_t*)"QSPI Initialization : OK.", LEFT_MODE);
}

BSP_QSPI_MemoryMappedMode();

 

to display an image use:


BSP_LCD_DrawBitmap(1, 1, (uint8_t *)(&logo));

 

modify the file STM32F746NGHx_FLASH.ld in the CubeWorkspace

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
    RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 307K
    QSPI (xrw) : ORIGIN = 0x90000000, LENGTH = 16M <<<<<<<< add this
    Memory_B1(xrw) : ORIGIN = 0x2004C000, LENGTH = 0x80
    Memory_B2(xrw) : ORIGIN = 0x2004C080, LENGTH = 0x80
    Memory_B3(xrw) : ORIGIN = 0x2004C100, LENGTH = 0x17d0
    Memory_B4(xrw) : ORIGIN = 0x2004D8D0, LENGTH = 0x17d0
}

/* Define output sections */
SECTIONS
{
    /* The startup code goes first into FLASH */
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector)) /* Startup code */
        . = ALIGN(4);
    } >FLASH

    .textqspi :              <<< add this section
    {
        . = ALIGN(4);
        _qspi_start = .; /* create a global symbol at qspi start */
        *(.textqspi) /* .textqspi sections */
        *(.textqspi*) /* .textqspi* sections */
        . = ALIGN(4);
        _qspi_end = .; /* define a global symbols at end of textqspi */
    } >QSPI


...................

    .ARM.attributes 0 : { *(.ARM.attributes) }
    .textqspi : { *(.textqspi) } >QSPI                     <<< add this
    .RxDecripSection : { *(.RxDescripSection) } >Memory_B1
    .TxDescripSection : { *(.TxDescripSection) } >Memory_B2
    .RxarraySection : { *(.RxBUF) } >Memory_B3
    .TxarraySection : { *(.TxBUF) } >Memory_B4
}

 

Now after Build project you should see a new section the the Build Analyzer, if you dont't see lines press Refresh .



If you are using the Cube IDE application to flash use with this set

If you are using the ST-LINK application to flash use with this set

0693W000006FZbaQAG.png

 

 

 

 

M-DAC VOLUME

Instead of use a PGA2310, MAS6116 or a MUSES72320 as Volume control is possible to use a MDAC (Multiplying Digital-to-Analog Converter).
Some companies like Mark Levinson use the MDAC chips and the result is like the previous chips but like MAS6116 and MUSES72320 is necessary an external op-amp.
Here the schematic found in the DAC8043 datasheet and follows some link to articles about.

The Mark Levinson in some models use the DAC8812 a MDAC with 16bit but also a 14 or 12 bits are enought to get a low error near to 0.01dB.
I have tested the MDAC on STM32 module using this code valid for DAC8812 with SPI interface.

#define VOL_MIN_MDAC (0)
#define VOL_MAX_MDAC (90)

float db_MDAC = 32.0;

case buttonUpClicked:
   if (db_MDAC < VOL_MAX_MDAC)
       db_MDAC+=1.0;

case buttonDownClicked:
    if (db_MDAC > VOL_MIN_MDAC)
        db_MDAC-=1.0

if (balance == 0)
{
    db_MDACL = db_MDAC;
    db_MDACR = db_MDAC;
}

float levelL16 = (65536.0 * pow(10.0, -db_MDACL / 20.0)) - 1;
float levelR16 = (65536.0 * pow(10.0, -db_MDACR / 20.0)) - 1;

BSP_SPI_Write_MDAC((uint16_t)levelL16, (uint16_t)levelR16);

 

void BSP_SPI_Write_MDAC(uint16_t levelL, uint16_t levelR)
{
    int addr_decode  = 3;
    int addr_decodeL = 1;
    int addr_decodeR = 2;

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);  // CS pin
    delayUs(10);
    HAL_SPI_Transmit(&spi, (uint8_t * )&addr_decodeL, 1, HAL_MAX_DELAY);

    uint16_t highByte = levelL >> 8;
    uint16_t lowBytetemp = levelL << 8;
    uint16_t lowByte = lowBytetemp >> 8;

    HAL_SPI_Transmit(&spi, (uint8_t * )&highByte, 1, HAL_MAX_DELAY);
    HAL_SPI_Transmit(&spi, (uint8_t * )&lowByte, 1, HAL_MAX_DELAY);

    delayUs(10);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // CS pin

    delayUs(200);

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); // CS pin
    delayUs(10);
    HAL_SPI_Transmit(&spi, (uint8_t * )&addr_decodeR, 1, HAL_MAX_DELAY);

    highByte = levelR >> 8;
    lowBytetemp = levelR << 8;
    lowByte = lowBytetemp >> 8;

    HAL_SPI_Transmit(&spi, (uint8_t * )&highByte, 1, HAL_MAX_DELAY);
    HAL_SPI_Transmit(&spi, (uint8_t * )&lowByte, 1, HAL_MAX_DELAY);

    delayUs(10);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // CS pin
}

 

 

 

 

CUBE IDE SETTINGS

To create the hex output file set this checkbox

or add this line

arm-none-eabi-objcopy -O ihex ${ProjName}.elf ${ProjName}.hex

  

To create the output files with the project name 

  

To have the project name define inside the code

 

void Show_Version()
{
    __attribute__((used))

    static char compilation_date[] = PROJECTNAME " " __DATE__ " " __TIME__;

    BSP_LCD_SetFont (&Font24);
    BSP_LCD_SetTextColor(LCD_COLOR_WHITE);
    BSP_LCD_SetBackColor(LCD_COLOR_TRANSPARENT);
    BSP_LCD_DisplayStringAt(10, 420, (uint8_t *)compilation_date, CENTER_MODE);
}

 

 

BACKLIGHT TRICKS

In many case the controller will be used with a stand-by operation so it is necessary switch-off the backlight.

For the STM32F469I-DISCOVERY to have a complete switch-off of the backlight you need to make a correction on STM32 module.

The 32F469IDISCOVERY Discovery board offers the option to control the EN pin by HW through port PA3. In such case, R117 must be removed and R119 soldered.

#define LCD_BL_CTRL_Pin GPIO_PIN_3
#define LCD_BL_CTRL_GPIO_Port GPIOA

__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = LCD_BL_CTRL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

HAL_GPIO_Init(LCD_BL_CTRL_GPIO_Port, &GPIO_InitStruct);

HAL_GPIO_WritePin(LCD_BL_CTRL_GPIO_Port, LCD_BL_CTRL_Pin, GPIO_PIN_RESET); /* backlight off */

HAL_GPIO_WritePin(LCD_BL_CTRL_GPIO_Port, LCD_BL_CTRL_Pin, GPIO_PIN_SET); /* backlight on */