Soft-SPI: Biblioteca para memória SPI-Flash em microcontroladores PSoC-4

Olá amigos! Em muitos projetos com microcontroladores podemos lançar mão de usar uma memória externa para armazenamento de logs, imagens de firmware para realização de bootloader e configurações, e outros usos diversos.

 

Neste artigo vou apresentar uma biblioteca implementada para a família de microcontroladores PSoC-4. Ela foi implementada utilizando o PSoC Creator e, embora a mesma seja para PSoC-4, com pequenas alterações pode ser utilizada com qualquer microcontrolador.

 

No Portal Embarcados há um excelente material sobre SPIs, destacando a série do Francesco Sacco, do Evandro Teixeira e outros.

 

 

SPI no PSoC-4

 

Os microcontroladores da família PSoC-4 são microcontroladores ARM Cortex-M0 com diversos periféricos interessantes. O foco deste artigo será dado somente à interface SPI.

 

Nos microcontroladores PSoC a interface SPI é implementada através de um componente chamado SCB (Serial Communication Block). Esse bloco é configurado e pode trabalhar como uma interface SPI, I2C ou USART.

 

Possíveis configurações do bloco SCB
Figura 1- Possíveis configurações do bloco SCB

 

A configuração do periférico pode ser realizada tanto de forma programática quanto através de Wizards presentes no PSoC Creator, o que facilita muito a implementação. Podemos construir uma solução completa utilizando esses blocos de comunicação serial, com rotinas de tratamento de interrupção fornecidas pelo desenvolvedor, baudrate variável, uso de DMA e outras possibilidades interessantes.

 

No projeto que estou trabalhando atualmente estou utilizando um CY8C4126AXI-M443. Devido a requisitos do projeto, loteei os 4 SCBs disponíveis e implementei uma biblioteca soft-SPI para realizar a comunicação com a memória SPI externa.

 

 

Interface com a memória SPI Flash externa

 

Estamos utilizando uma memória serial da Adesto com 1 Mbit de capacidade e com uma feature muito interessante: suporte a dual read. Esta feature não será utilizada no nosso projeto.

 

Para representar este componente no PSoC Creator utilizei as ferramentas de desenho mais simples da IDE, chegando ao resultado apresentado abaixo:

 

Representação da memória SPI Flash AT25DN011 no PSoC Creator
Figura 2 - Representação da memória SPI Flash AT25DN011 no PSoC Creator

 

O ponto importante aqui é realizar o correto mapeamento dos GPIOs conectados à memória SPI. Este mapeamento pode ser deixado por parte da IDE, o que não é muito recomendado, pois o PSoC Creator pode alocar qualquer GPIO para estas funções; ou podemos usar o Wizard para realizar o correto mapeamento dos pinos, como ilustrado na figura abaixo:

 

Ferramenta de configuração de recursos do PSoC.
Figura 3 - Ferramenta de configuração de recursos do PSoC.

 

No momento que o desenvolvedor compila pela primeira vez a aplicação, o PSoC Creator gera a estrutura mínima para manipular os ports individualmente. A figura a seguir mostra a hierarquia de arquivos criada pelo PSoC Creator para a nossa interface soft-SPI:

 

Hierarquia de arquivos do nosso módulo soft-SPI.
Figura 4 - Hierarquia de arquivos do nosso módulo soft-SPI.

 

A interface da biblioteca de GPIOs criada pela Cypress fornece funções de configuração, leitura, escrita e de callback para interceptação de ISRs. Para a nossa biblioteca, não nos preocuparemos muito com certos aspectos, como ISRs, e, por isso, vamos somente utilizar as seguintes rotinas:

void Flash_CLK_Write(uint8 value);
uint8   Flash_CLK_Read(void);

 

O único aspecto que tive que melhorar foi o número de ciclos de instruções utilizado pela biblioteca padrão da Cypress para acesso a um GPIO. A implementação gerada pelo PSoC Creator para o GPIO não é muito bem otimizada e, por isso, realizamos o acesso direto aos registradores do GPIO utilizando alguns aliases gerados pelo PSoC Creator. No exemplo abaixo mostramos as implementações correspondentes para Flash_CLK_Write  e Flash_CLK_Read, que são as SetValue  e GetValue :

#define Flash_CLK_SetValue( x )          \
do                                       \
{                                        \
    if ( (x) )                           \
    {                                    \
        Flash_CLK_DR |= Flash_CLK_MASK;  \
    }                                    \
    else                                 \
    {                                    \
        Flash_CLK_DR &= ~Flash_CLK_MASK; \
    }                                    \
}while(0)                                \

#define Flash_CLK_GetValue( ) \
((Flash_CLK_DR & Flash_CLK_MASK) >> Flash_CLK_SHIFT)

 

 

Implementação da biblioteca Soft-SPI para Adesto AT25DN011

 

Esta memória flash é um pouco diferente das memórias EEPROM I2C e SPI que são largamente utilizadas em projetos de sistemas embarcados. Ao invés de termos uma sequência contígua de bytes acessáveis individualmente, temos uma organização de páginas como ilustrada na figura a seguir:

 

Arquitetura de memória do Adesto AT25DN011.
Figura 5 - Arquitetura de memória do Adesto AT25DN011.

 

O outro aspecto que diferencia as memórias Flashs das ordinárias EEPROM e etc é o fato de que precisamos enviar comandos para realizar operações nela. Um exemplo que podemos dar é a sequência para realizar a leitura de um buffer da memória: inicialmente enviamos um comando de leitura indicando qual o endereço que queremos ler e a seguir realizamos a leitura sequencial dos dados presentes na memória flash. Sequências semelhantes ocorrem com os outros comandos disponibilizados por esta memória, como ilustrado na tabela 6-1 do datasheet.

 

Para colocar a memória em funcionamento, permitindo realizar operações de escrita, leitura, apagar uma página, vamos implementar somente um subconjunto de comandos: Page Program, Read Array, Read ID, Write Enable, Write Disable, Read Status Register e Page Erase (vide datasheet).

 

Para deixar a implementação agnóstica de processador, resolvemos construir uma interface (vide o fantástico artigo do Felipe Lavratti aqui) dada pela estrutura struct TSwSpi apresentada abaixo:

struct TSwSpi
{
    uint32_t    cPol:1;
    uint32_t    cPha:1;
    
    TSpiMode    mode;
    
    uint32_t    (*ReadMISO)( TSwSpi* dev );
    void        (*WriteMOSI)( TSwSpi* dev, BYTE bData );
    void        (*WriteCS)( TSwSpi* dev, BYTE bState );
    void        (*WriteClockPin)( TSwSpi* dev, BYTE bState );
    uint32_t    (*ReadClockPin)( TSwSpi* dev );
    void        (*Clock)( TSwSpi* dev );
    void        (*EnableCS)( TSwSpi* dev );
    void        (*DisableCS)( TSwSpi* dev );
    uint32_t    (*Send)( TSwSpi* dev, const BYTE bData );
    uint32_t    (*Receive)( TSwSpi* dev, BYTE* pbData );
   
    uint32_t    (*init)( TSwSpi* dev, TSpiMode xMode );
    uint32_t    (*deinit)( TSwSpi* dev );
    uint32_t    (*write)( TSwSpi* dev, const DWORD dwAddr, BYTE* pbData, const size_t szSize );
    uint32_t    (*read)( TSwSpi* dev, const DWORD dwAddr, BYTE* pbData, size_t szSize ); 
    uint32_t    (*readid)( TSwSpi* dev, BYTE* pbBuffer, const size_t szSize );
    uint32_t    (*wren)( TSwSpi* dev );
    uint32_t    (*wrdi)( TSwSpi* dev );
    uint32_t    (*rdst)( TSwSpi* dev, void* reg );
};

 

A estrutura TSwSpi possui ponteiros de funções para acesso ao barramento SPI e rotinas de leitura e escrita da memória Flash.

 

Abaixo apresento a implementação completa da biblioteca de acesso à memória flash SPI:

/**
 *  @file   Flash_AT25DN011.c
 *  @brief  flash AT25DN011 command definitions. 
 *      Datasheet https://www.adestotech.com/wp-content/uploads/DS-AT25DN011_038.pdf
 *  @author Rafael Dias Menezes <[email protected]>
 *  @date   december/2017
 */

#include "Flash_AT25DN011.h"
#include "Flash_CLK.h"
#include "Flash_SI.h"
#include "Flash_SO.h"
#include "Flash_CS.h"
#include <CyLib.h>

/*****************************************************************************/
/* AT25DN011 JEDEC ID */
const BYTE AT25DN011_JDECID[] = { 0x1f, 0x42, 0x00, 0x00 };

/*****************************************************************************/

#define Flash_SI_SetValue( x )                          \
            do                                          \
            {                                           \
                if ( (x) )                              \
                {                                       \
                    Flash_SI_DR |= Flash_SI_MASK;       \
                }                                       \
                else                                    \
                {                                       \
                    Flash_SI_DR &= ~Flash_SI_MASK;      \
                }                                       \
            }while(0)                                   \
                                                        
                                                        
#define Flash_SI_GetValue( )                            \
            ((Flash_SI_DR & Flash_SI_MASK) >> Flash_SI_SHIFT)

#define Flash_SO_SetValue( x )                          \
            do                                          \
            {                                           \
                if ( (x) )                              \
                {                                       \
                    Flash_SO_DR |= Flash_SO_MASK;       \
                }                                       \
                else                                    \
                {                                       \
                    Flash_SO_DR &= ~Flash_SO_MASK;      \
                }                                       \
            }while(0)                                   \
                                                        
                                                        
#define Flash_SO_GetValue( )                            \
            ((Flash_SO_DR & Flash_SO_MASK) >> Flash_SO_SHIFT)

#define Flash_CS_SetValue( x )                          \
            do                                          \
            {                                           \
                if ( (x) )                              \
                {                                       \
                    Flash_CS_DR |= Flash_CS_MASK;       \
                }                                       \
                else                                    \
                {                                       \
                    Flash_CS_DR &= ~Flash_CS_MASK;      \
                }                                       \
            }while(0)                                   \
                                                        
#define Flash_CS_GetValue( )                            \
            ((Flash_CS_DR & Flash_CS_MASK) >> Flash_CS_SHIFT)

#define Flash_CLK_SetValue( x )                         \
            do                                          \
            {                                           \
                if ( (x) )                              \
                {                                       \
                    Flash_CLK_DR |= Flash_CLK_MASK;     \
                }                                       \
                else                                    \
                {                                       \
                    Flash_CLK_DR &= ~Flash_CLK_MASK;    \
                }                                       \
            }while(0)                                   \

#define Flash_CLK_GetValue( )                           \
            ((Flash_CLK_DR & Flash_CLK_MASK) >> Flash_CLK_SHIFT)


/*****************************************************************************/

static void AT25DN011_Busy( TSwSpi* dev )
{
    do                                              
    {                                               
        Flash_AT25DN011_Status_Reg xStatusReg;      
        dev->rdst( dev, (void*)&xStatusReg );       
                                                    
        if ( xStatusReg.BSY1 )                      
            CyDelay( 1 );                           
        else                                        
            break;                                  
    }while( 1 );                                     
    return;
}

#define Flash_Busy() AT25DN011_Busy( dev )

/*****************************************************************************/

uint32_t AT25DN011_ReadMISO(TSwSpi* dev)
{
    uint32_t dwRet = (uint32_t)-1;
    if ( dev ) 
    {
        dwRet = Flash_SO_Read();
    }
    return dwRet;
}

/*****************************************************************************/

void AT25DN011_WriteMOSI(TSwSpi* dev, BYTE bState)
{
    if ( dev ) 
    {
        Flash_SI_SetValue( bState != 0 );
    }
    return;
}

/*****************************************************************************/

void AT25DN011_WriteCS(TSwSpi* dev, BYTE bState)
{
    if ( dev ) 
    {
        Flash_CS_SetValue( bState != 0 );
    }
    return;
}

/*****************************************************************************/

#define AT25DN011_CLOCK_DELAY 1

void AT25DN011_Clock(TSwSpi* dev)
{
    if ( dev )
    {
        CyDelayUs( AT25DN011_CLOCK_DELAY );
        Flash_CLK_SetValue( !Flash_CLK_GetValue() ); 
        CyDelayUs( AT25DN011_CLOCK_DELAY );
        Flash_CLK_SetValue( !Flash_CLK_GetValue() ); 
    }
    return;
}

/*****************************************************************************/

void AT25DN011_WriteClockPin(TSwSpi* dev, BYTE bData)
{
    if ( dev )
    {
        Flash_CLK_SetValue( bData );
    }
    return;
}

/*****************************************************************************/

uint32_t    AT25DN011_ReadClockPin(TSwSpi* dev)
{
    uint32_t dwRet = (uint32_t)-1;
    if ( dev )
    {
        dwRet = Flash_CLK_GetValue( );
    }
    return dwRet;    
}

/*****************************************************************************/

void AT25DN011_EnableCS(TSwSpi* dev)
{
    if ( dev )
    {
        Flash_CS_SetValue( SPI_SW_ENABLE_CS );
    }
}

/*****************************************************************************/

void AT25DN011_DisableCS(TSwSpi* dev)
{
    if ( dev )
    {
        Flash_CS_SetValue( SPI_SW_DISABLE_CS );
    }
}

/*****************************************************************************/

uint32_t AT25DN011_Send(TSwSpi* dev, const BYTE bData)
{
    uint32_t dwRet = ENXIO;
    
    if ( dev )
    {
        BYTE bBit = 0x80;
        
        dev->WriteClockPin( dev, dev->cPha );
        
        for ( size_t szBitCounter = 0; szBitCounter < 8; szBitCounter++ )
        {
            if ( !dev->cPha )
            {
                dev->WriteMOSI( dev, bData & bBit );
                dev->Clock( dev );
            }
            else
            {
                dev->Clock( dev );
                dev->WriteMOSI( dev, bData & bBit );
            }
            
            bBit >>= 1;
        }
        
        dwRet = 0;
    }
    return dwRet;
}

/*****************************************************************************/

uint32_t AT25DN011_Receive(TSwSpi* dev, BYTE* pbData)
{
    uint32_t dwRet = ENXIO;
    if ( dev )
    {
        if ( pbData )
        {
            uint8_t bData = 0;
            volatile int iCounter;
        
            for ( iCounter = 0; iCounter < 8; iCounter++ )
            {
                AT25DN011_WriteClockPin( dev, !AT25DN011_ReadClockPin( dev ));
                CyDelayUs( 10 );
                bData |= (dev->ReadMISO( dev ) << (7 - iCounter));
                AT25DN011_WriteClockPin( dev, !AT25DN011_ReadClockPin( dev ) );
                CyDelayUs( 10 );                
            }
            
            *pbData = bData;
            dwRet = 0;
        }
    }
    return dwRet;    
}

/*****************************************************************************/

uint32_t AT25DN011_PageErase(TSwSpi* dev, const DWORD dwAddr )
{
    uint32_t dwRet = ENXIO;
    if ( dev )
    {
        /* send the write enable command */
        AT25DN011_wren( dev );        
        
        DWORD dwPageAddr = dwAddr & 0x0001FF00;
        BYTE bCmdBuffer[4];
        
        bCmdBuffer[0] = AT25DN011_Page_Erase;
        bCmdBuffer[1] = (BYTE)( dwPageAddr >> 16 );
        bCmdBuffer[2] = (BYTE)( dwPageAddr >>  8 );
        bCmdBuffer[3] = (BYTE)( dwPageAddr );
        
        dev->EnableCS( dev );
        dev->Send( dev, bCmdBuffer[0] );
        dev->Send( dev, bCmdBuffer[1] );
        dev->Send( dev, bCmdBuffer[2] );
        dev->Send( dev, bCmdBuffer[3] );
        dev->DisableCS( dev );
        
        Flash_Busy();
        
        /* send the write disable command */
        AT25DN011_wrdi( dev );        
    }
    return dwRet;
}

/*****************************************************************************/

uint32_t AT25DN011_init(TSwSpi* dev, TSpiMode  mode )
{
    uint32_t dwRet = ENXIO;
    if ( dev )
    {
        dev->mode = mode;
        if ( SPI_MODE0 == dev->mode )
        {
            dev->cPha = 0;
            dev->cPol = 0;
        }
        
        if ( SPI_MODE2 == dev->mode )
        {
            dev->cPha = 1;
            dev->cPol = 0;            
        }
        
        if ( !dev->cPol )
        {
            dev->WriteClockPin( dev, 0 );
        }
        else
        {
            dev->WriteClockPin( dev, 1 );
        }

        dev->WriteCS( dev, SPI_SW_DISABLE_CS );
        
        BYTE bBuffer[4];
        if ( 0 == AT25DN011_readid( dev, bBuffer, sizeof( bBuffer )) ) 
        {
            if ( 0 == memcmp( bBuffer, AT25DN011_JDECID, sizeof( AT25DN011_JDECID )) )
            {
                dwRet = 0;
            }
        }     
    }
    
    return dwRet;
}

/*****************************************************************************/

uint32_t    AT25DN011_deinit( TSwSpi* dev )
{
    (void)dev;
    return 0;
}

/*****************************************************************************/

uint32_t AT25DN011_write(TSwSpi* dev, const DWORD dwAddr, BYTE* pbBuffer, const size_t szSize)
{
    uint32_t dwRet = ENXIO;
    
    if ( ( NULL != dev ) && ( NULL != pbBuffer ) )
    {
        BYTE bBuffer[AT25DN011_PAGE_SIZE];
        uint16_t iPageCount;
        const uint16_t iTotalPages = (( ( dwAddr % AT25DN011_PAGE_SIZE ) + szSize ) / AT25DN011_PAGE_SIZE ) + 1;
        uint32_t dwRelAddr = 0;
        
        for ( iPageCount = 0; iPageCount < iTotalPages; iPageCount++ )
        {
            uint32_t dwBytestoWrite;
            uint32_t dwPageAddr =   ((dwAddr + dwRelAddr) / AT25DN011_PAGE_SIZE) * AT25DN011_PAGE_SIZE;
            uint32_t dwRelMemAddr = (dwAddr + dwRelAddr) % AT25DN011_PAGE_SIZE; 
            
            dwBytestoWrite = AT25DN011_PAGE_SIZE;
            
            if ( iPageCount == 0 )
            {
                if ( iTotalPages > 1 )
                {
                    dwBytestoWrite = AT25DN011_PAGE_SIZE - (dwRelMemAddr);
                }
                else
                {
                    dwBytestoWrite = szSize;
                }
            }
            else
            {
                if ( iPageCount == (iTotalPages - 1) )
                {
                    dwBytestoWrite = szSize - dwRelAddr;
                }                
            }
            
            Flash_Busy();
            
            // read a buffer from external flash memory
            AT25DN011_read( dev, dwPageAddr, (void*)bBuffer, AT25DN011_PAGE_SIZE );
            
            // copy the dwBytestoWrite bytes from pbBuffer to bBuffer
            memcpy( &bBuffer[dwRelMemAddr],  pbBuffer + dwRelAddr, dwBytestoWrite );
            
            // issue a page erase command 
            AT25DN011_PageErase( dev, dwPageAddr );            

            const BYTE bCmdBuffer[] = 
            {        
                [0] = (BYTE)( AT25DN011_Page_Program    ),
                [1] = (BYTE)( dwPageAddr >> 16          ),
                [2] = (BYTE)( dwPageAddr >>  8          ),
                [3] = (BYTE)( dwPageAddr >>  0          ),
            };

            // send the write enable command 
            AT25DN011_wren( dev );  
            Flash_Busy();
            
            dev->EnableCS( dev );
            dev->Send( dev, bCmdBuffer[0] );
            dev->Send( dev, bCmdBuffer[1] );
            dev->Send( dev, bCmdBuffer[2] );
            dev->Send( dev, bCmdBuffer[3] );
            
            for ( volatile uint16_t uiByteCounter = 0; uiByteCounter < AT25DN011_PAGE_SIZE; uiByteCounter++ )
            {
                dev->Send( dev, bBuffer[uiByteCounter] );
            }            
            
            dev->DisableCS( dev );
            
            Flash_Busy();
            
            // send the write disable command 
            AT25DN011_wrdi( dev );              

            dwRelAddr += dwBytestoWrite;
        }
        
        if ( iTotalPages == iPageCount )
        {
            Flash_Busy();  
            dwRet = 0;
        }
    }
    return dwRet;
}

/*****************************************************************************/

uint32_t AT25DN011_read(TSwSpi* dev, const DWORD dwAddr, BYTE* pbBuffer, size_t szSize)
{
    size_t szCounterData = 0;
    if ( dev )
    {
        if ( pbBuffer )
        {
            const DWORD addr = (dwAddr & 0x00FFFFFF);
            
            const BYTE bCmdBuffer[] = 
            {
              [0] = (BYTE)(AT25DN011_Read_Array),
              [1] = (BYTE)( addr >> 16 ),
              [2] = (BYTE)( addr >> 8 ),
              [3] = (BYTE)( addr ),
              [4] = (BYTE)( 0x55 ),
            };
            
            dev->EnableCS(dev);
            
            dev->Send( dev, bCmdBuffer[0] );
            dev->Send( dev, bCmdBuffer[1] );
            dev->Send( dev, bCmdBuffer[2] );
            dev->Send( dev, bCmdBuffer[3] );
            
            if ( AT25DN011_Read_Array == bCmdBuffer[0] )
            {
                dev->Send( dev, bCmdBuffer[4] );
            }
            
            for ( volatile unsigned short int counter = 0; counter < szSize; counter++ )
            {
                dev->Receive( dev, pbBuffer + counter );                 
            }
            
            szCounterData = szSize;
            
            dev->DisableCS(dev);
        }
    }
    return szCounterData;
}

/*****************************************************************************/

uint32_t    AT25DN011_readid( TSwSpi* dev, BYTE* pbBuffer, const size_t szSize )
{
    uint32_t dwRet = ENXIO;
    if ( dev )
    {
        if ( pbBuffer )
        {
            if ( szSize >= 4 )
            {
                uint8_t bBuffer[4];
                const uint8_t bCmdBuffer[] = 
                {
                    [0] = (BYTE)(AT25DN011_Read_ManufID),
                };
                
                dev->EnableCS( dev );
                dev->Send( dev, bCmdBuffer[0] );
                dev->Receive( dev, &bBuffer[0] );
                dev->Receive( dev, &bBuffer[1] );
                dev->Receive( dev, &bBuffer[2] );
                dev->Receive( dev, &bBuffer[3] );
                dev->DisableCS( dev );

                memcpy( pbBuffer, &bBuffer[0], 4 );
                
                dwRet = 0;
            }
        }
    }
    
    return dwRet;
}

/*****************************************************************************/

uint32_t    AT25DN011_wren( TSwSpi* dev )
{
    DWORD dwRet = ENXIO;
    
    if ( dev )
    {
        const uint8_t bCmdBuffer[] = 
        {
            [0] = (BYTE)(AT25DN011_Write_Enable),
        };        
        
        dev->EnableCS( dev );
        dev->Send( dev, bCmdBuffer[0] );        
        dev->DisableCS( dev );
    }
    return dwRet;
}

/*****************************************************************************/

uint32_t    AT25DN011_wrdi( TSwSpi* dev )
{
    DWORD dwRet = ENXIO;
    
    if ( dev )
    {
        const uint8_t bCmdBuffer[] = 
        {
            [0] = (BYTE)(AT25DN011_Write_Disable),
        };           
        
        dev->EnableCS( dev );
        dev->Send( dev, bCmdBuffer[0] );        
        dev->DisableCS( dev );
    }
    return dwRet;    
}

/*****************************************************************************/

uint32_t    AT25DN011_rdst( TSwSpi* dev, void* reg )
{
    DWORD dwRet = ENXIO;
    
    volatile Flash_AT25DN011_Status_Reg* xStatusRegAT25DN011 = (Flash_AT25DN011_Status_Reg*)reg;
    
    if ( dev )
    {
        if ( reg )
        {
            uint8_t cBuffer[2];
            const uint8_t bCmdBuffer[] = 
            {
                [0] = AT25DN011_Read_Status_Register,
            };
            
            dev->EnableCS( dev );
            dev->Send( dev, bCmdBuffer[0] );    
            dev->Receive( dev, &cBuffer[0] );
            dev->Receive( dev, &cBuffer[1] );
            dev->DisableCS( dev );
        
            xStatusRegAT25DN011->reg1 = cBuffer[0];
            xStatusRegAT25DN011->reg2 = cBuffer[1];
        }
    }
    return dwRet;    
}

/*****************************************************************************/

 

A única rotina que vale a pena revisitar é a AT25DN011_write (linhas 349-438). Para realizar a escrita na memória, dado o seu endereço, é descobrir qual é a página que devemos acessar. Este cálculo é feito na linha 363 utilizando-se do tamanho do buffer de escrita da memória e do endereço que pretendemos acessar. O outro dado importante é descobrir qual é o endereço relativo no buffer do dado que queremos escrever, que é realizado na linha 364 através de uma conta bem simples. Após esses cálculos, esperamos a memória flash ficar disponível, através de uma chamada à macro Flash_Busy(). O próximo passo é realizar a leitura do buffer da memória (linha 390) para um buffer temporário; escrever o dado que queremos salvar na flash no buffer temporário, observando o offset (linha 393), apagar a página na memória flash (linha 396), habilitar escrita (linha 407) e, finalmente enviamos os dados para serem programados até completar a página (linhas 416-419). Este processo é realizado para cada página que for escrita na memória flash.

 

 

Migrando esta biblioteca para um outro microcontrolador

 

A implementação apresentada no item anterior é agnóstico de processador. Se quisermos alterar o microcontrolador/microprocessador de destino, deveremos alterar as rotinas listadas abaixo:

uint32_t (*ReadMISO)( TSwSpi* dev ); 
void (*WriteMOSI)( TSwSpi* dev, BYTE bData ); 
void (*WriteCS)( TSwSpi* dev, BYTE bState ); 
void (*WriteClockPin)( TSwSpi* dev, BYTE bState ); 
uint32_t (*ReadClockPin)( TSwSpi* dev ); 
void (*Clock)( TSwSpi* dev ); 
void (*EnableCS)( TSwSpi* dev ); 
void (*DisableCS)( TSwSpi* dev ); 
uint32_t (*Send)( TSwSpi* dev, const BYTE bData ); 
uint32_t (*Receive)( TSwSpi* dev, BYTE* pbData );

 

As outras rotinas do módulo Flash_AT25DN011 são independentes de processador.

 

Outro aspecto importante de destacar é a facilidade de alterar a memória flash de destino para qualquer outra SPI Flash que possua um set de comandos semelhantes ao requerido pela AT25DN011.

 

 

Código de exemplo

 

Abaixo mostro um código de exemplo de uso da biblioteca:

int main(void)
{
  CyGlobalIntEnable; /* Enable global interrupts. */

  TSwSpi* xFlashSpi = GetFlashInterface( FlashAT25DN011 );
  xFlashSpi->init( xFlashSpi, SPI_MODE0 );

  /* Place your initialization/startup code here (e.g. MyInst_Start()) */
  struct 
  {
    uint8_t bBuffer[16];
    uint32_t dwAddr;
  } xData1, xData2, xData3;

  xData1.dwAddr = 230;
  memset( xData1.bBuffer, 0x43, sizeof( xData1.bBuffer ));
  xFlashSpi->write( xFlashSpi, xData1.dwAddr, xData1.bBuffer, sizeof( xData1.bBuffer ));
  memset( xData1.bBuffer, 0xEE, sizeof( xData1.bBuffer ));
  xFlashSpi->read( xFlashSpi, xData1.dwAddr, xData1.bBuffer, sizeof( xData1.bBuffer ));

  xData2.dwAddr = xData1.dwAddr + sizeof( xData1.bBuffer );
  memset( xData2.bBuffer, 0x44, sizeof( xData2.bBuffer ));
  xFlashSpi->write( xFlashSpi, xData2.dwAddr, xData2.bBuffer, sizeof( xData2.bBuffer ));
  memset( xData2.bBuffer, 0xEE, sizeof( xData2.bBuffer ));
  xFlashSpi->read( xFlashSpi, xData2.dwAddr, xData2.bBuffer, sizeof( xData2.bBuffer )); 

  xData3.dwAddr = xData2.dwAddr + sizeof( xData2.bBuffer );
  memset( xData3.bBuffer, 0x45, sizeof( xData3.bBuffer ));
  xFlashSpi->write( xFlashSpi, xData3.dwAddr, xData3.bBuffer, sizeof( xData3.bBuffer ));
  memset( xData3.bBuffer, 0xEE, sizeof( xData3.bBuffer ));
  xFlashSpi->read( xFlashSpi, xData3.dwAddr, xData3.bBuffer, sizeof( xData3.bBuffer ));

  uint8_t bBuffer1[ sizeof( xData1.bBuffer ) + sizeof( xData2.bBuffer ) + sizeof( xData3.bBuffer )];
  xFlashSpi->read( xFlashSpi, xData1.dwAddr, bBuffer1, sizeof( bBuffer1 ));

  uint8_t bBuffer2[600];
  memset( bBuffer2, 0x66, sizeof( bBuffer2 ));
  xFlashSpi->write( xFlashSpi, xData3.dwAddr + 100, bBuffer2, sizeof( bBuffer2 ));

  memset( bBuffer2, 0x55, sizeof( bBuffer2 ));
  xFlashSpi->read( xFlashSpi, xData3.dwAddr + 100, bBuffer2, sizeof( bBuffer2 )); 

  for(;;)
  {
    /* Place your application code here. */
  }
}

 

Aqui estamos realizando a escrita e leitura de três buffers de 16 bytes em duas páginas consecutivas.

 

A imagem a seguir mostra os dados presentes na memória RAM do microcontrolador após a execução deste programa de teste: 

 

Resultado do debug.
Figura 6 - Resultado do debug.

 

Note o conteúdo da variável buffer1, nela temos presente o conteúdo gravado na memória flash a partir do endereço 230 até o 277.

 

 

Conclusão

 

Neste artigo apresentamos a implementação de uma biblioteca para acesso a uma memória flash serial por meio de uma SPI emulada. O código foi implementado pensando nos microcontroladores PSoC-4 da Cypress, mas pode ser portado sem muita dificuldade para qualquer outro processador. Os códigos fontes estão disponíveis no github.

 

Convido os leitores a testarem essa implementação com outros processadores e meçam a performance do mesmo.

 

 

Saiba mais

 

Biblioteca SPI para a placa FRDM-KL25Z

Comunicação SPI em Linux

Série "Comunicação SPI"

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Rafael Dias
Sou Bacharel em Física formado pelo Instituto de Física da USP, mestre em Engenharia Elétrica, com ênfase em materiais nanoestruturados pela Escola Politécnica da USP e também Técnico em Automação da Manufatura pela Escola SENAI Anchieta. Trabalho com desenvolvimento de software, firmware e me arrisco com eletrônica analógica para instrumentação e controle. Nos tempos livres gosto de dar uma espairecida e pedalar um pouco.

Deixe um comentário

avatar
 
  Notificações  
Notificar