Apresentando a plataforma de desenvolvimento móvel FPGA iCE40 UltraPlus da Lattice Semiconductor

Introdução



Bom dia amigos! Recentemente, no trabalho, recebemos uma placa de plataforma de desenvolvimento móvel iCE40 UltraPlus da Lattice Semiconductor. De acordo com os desenvolvedores no site oficial do iCE40 UltraPlus, o MDP é uma placa com 4 FPGAs iCE40 UltraPlus, cada um controlando seu próprio conjunto de periféricos. O conjunto inclui:



  • display móvel com resolução de 240x240 com interface MIPI DSI;
  • sensor de imagem com resolução de 640x480 (OVM7692);
  • microfones de baixa potência no valor de 4 peças;
  • Módulo BLE para transmissão de dados sem fio;
  • memória Flash SPI programável;
  • pacote de vários sensores (pressão, bússola, giroscópio e acelerômetro);
  • bem, todos os tipos de botões e lâmpadas.


O legal dessa baleia é que, com a ajuda de pacotes de software especiais, você pode implantar redes neurais para trabalhar com vídeo e som. E isso sem mencionar o fato de que os FPGAs da Lattice são de baixo consumo de energia, são pequenos e são razoavelmente baratos.



UltraPlus MDP







Como caso de teste, pisque o LED RGB (D13 no diagrama, destacado em vermelho na imagem à esquerda). Após revisar a documentação, concluímos que o LED é controlado pelo FPGA número U3 (destacado em vermelho na imagem à direita). Também aprendemos com a documentação que o LED é controlado por um modulador PWM integrado e um driver atual.



Tomamos nota desta informação.



Configurando o quadro e escrevendo um programa



Há um grupo de jumpers na placa, com os quais você pode selecionar o FPGA que precisa ser atualizado para funcionar com o grupo selecionado de dispositivos periféricos. Estamos interessados ​​em três grupos de jumpers responsáveis ​​por puxar a fonte de alimentação para o LED e programar o FPGA desejado.







O procedimento é o seguinte:



  1. Coloque a chave SW5 na posição ON / OFF
  2. Dois jumpers em J19 horizontalmente
  3. J26 , 1-2 3-4 ( . , )
  4. J17, J25, J27 9-10 ( )


Sim, eu entendo, é tudo chato, mas sem isso não dá certo.



Além disso, para conectar o gerador de sinal de clock, é necessário colocar o jumper J23 na posição 2-3 (a numeração vai de cima).







Agora o programa. Para criar um arquivo de bits para o firmware iCE40 UltraPlus MDP, você precisa do ambiente de desenvolvimento Lattice iCE cube 2 ( link para a página do produto ) e atualizar a própria placa Programmer and Deployment Tool . O produto é licenciado, mas após o registro, a licença pode ser obtida aqui www.latticesemi.com/Support/Licensing/DiamondAndiCEcube2SoftwareLicensing/iceCube2



O editor no IDE é muito inconveniente, então escrevi em Sublime Text, mas para cada um no seu.



Aqui está um esquema geral que deu uma compreensão do que e onde fazer:







Portanto, o modulador PWM e o driver atual, que mencionei anteriormente, surgiram. Esses dois dispositivos são módulos internos. É necessário escrever um dispositivo de controle lógico e enviar dados para que toda a cozinha funcione corretamente. Vamos começar em ordem, descrevendo a "caixa preta":



entity DriverRGB is
	port (
		-- RGB Led:
		LED0 	: out std_logic;
		LED1 	: out std_logic;
		LED2 	: out std_logic );
end DriverRGB;


A inicialização da sincronização está faltando na caixa preta. Para isso, é utilizado um módulo interno, que é declarado da seguinte forma:



-- Generator clock:
component SB_HFOSC is
	generic (
		CLKHF_DIV	: string := "0b00" );
	port (
		CLKHFPU	: in std_logic;
		CLKHFEN	: in std_logic;

		CLKHF 	: out std_logic );
end component;


imagemEste módulo pode gerar um sinal com frequências de 48MHz, 24MHz, 12MHz e 6MHz. O parâmetro CLKHF_DIV é responsável pelo fator de divisão de frequência ("0b00", "0b01", "0b10", "0b11", respectivamente). CLKHFPU e CLKHFEN permitem que o módulo funcione. CLKHF - sinal de relógio.



A seguir, declaramos o modulador PWM e o driver atual:



-- Embedded PWM IP:
component SB_LEDDA_IP is
	port (
		LEDDCS		: in std_logic;
		LEDDCLK		: in std_logic;
		LEDDDAT7	: in std_logic;
		LEDDDAT6	: in std_logic;
		LEDDDAT5	: in std_logic;
		LEDDDAT4	: in std_logic;
		LEDDDAT3	: in std_logic;
		LEDDDAT2	: in std_logic;
		LEDDDAT1	: in std_logic;
		LEDDDAT0	: in std_logic;
		LEDDADDR3	: in std_logic;
		LEDDADDR2	: in std_logic;
		LEDDADDR1	: in std_logic;
		LEDDADDR0	: in std_logic;
		LEDDDEN 	: in std_logic;
		LEDDEXE		: in std_logic;
		LEDDRST		: in std_logic;

		PWMOUT0		: out std_logic;
		PWMOUT1		: out std_logic;
		PWMOUT2		: out std_logic;
		LEDDON		: out std_logic );
end component;


-- RGB Driver:
component SB_RGBA_DRV is
	generic (
		CURRENT_MODE	: string := "0b0";
		RGB0_CURRENT	: string := "0b000000";
		RGB1_CURRENT	: string := "0b000000";
		RGB2_CURRENT	: string := "0b000000" );
	port (
		CURREN		: in std_logic;
		RGBLEDEN	: in std_logic;
		RGB0PWM		: in std_logic;
		RGB1PWM		: in std_logic;
		RGB2PWM		: in std_logic;

		RGB0 		: out std_logic;
		RGB1 		: out std_logic;
		RGB2 		: out std_logic );
end component;


imagemO modulador PWM precisa alimentar o endereço e os dados que são responsáveis ​​pelos modos de operação do LED e alguns sinais de controle. As saídas são então processadas pelo driver de corrente RGB, que já acende o LED.


O driver atual processa os dados do modulador PWM e ajusta a corrente fornecida ao LED. Os parâmetros RGB0_CURRENT, RGB1_CURRENT, RGB2_CURRENT definem a quantidade de corrente para cada cor. CURRENT_MODE - modo de energia (cheio ou meio).



imagem



Sim Legal. Existem endereços, existem dados. Bem, o que enviar a eles? Em geral, os desenvolvedores do Lattice em sua documentação fornecem uma descrição bastante detalhada, mas é bastante volumosa. Vou tentar condensar tudo em algumas linhas de descrição e código para maior clareza.



O modulador PWM espera 9 endereços. Cada um deles é responsável por uma função específica para manter o LED funcionando. Abaixo está uma tabela mostrando os valores e nomes dos endereços:







Para enviar dados, implementamos a máquina de estados finitos:



type LED_Driver is (IDLE, LEDDBR, LEDDONR, LEDDOFR, LEDDBCRR, LEDDBCFR, LEDDPWRR, LEDDPWRG, LEDDPWRB, LEDDCR0, DONE);


A primeira etapa é gravar dados no registrador LEDDBR. Ele armazena o valor da frequência de clock PWM. É considerado da seguinte forma:



Register Value N = Fsys/64kHz-1


A estrutura do registro de dados se parece com isto: Os







dois bits mais significativos para o valor da frequência serão adicionados quando viramos para o registro LEDDCR0.



when LEDDBR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1001";
		DAT_Bits(7 downto 0)	<= "11101101"; --     (   )
		PWM_state_next		<= LEDDONR;


O registro LEDDONR registra o tempo em que o LED está ativo. A documentação contém uma tabela de correspondência a qual conjunto de bits pertence a um determinado tempo de queima do LED.



when LEDDONR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1010";
		DAT_Bits(7 downto 0)	<= "00010001"; --    (0.5 c)


O registro LEDDOFR contém dados por quanto tempo o LED está inativo. Exatamente os mesmos valores de LEDDONR.



when LEDDOFR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1011";
		DAT_Bits(7 downto 0)	<= "00010001"; --    (0.5 c)
		PWM_state_next		<= LEDDBCRR;


LEDDBCRR - dados sobre a duração do LED soft-on.



when LEDDBCRR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0101";
		DAT_Bits(7)		<= '1'; --   ()
		DAT_Bits(6)		<= '1'; --   
		DAT_Bits(5)		<= '1'; --    
		DAT_Bits(4)		<= '0'; -- RESERVED
		DAT_Bits(3 downto 0)	<= "0011"; --   (0.5 )
		PWM_state_next		<= LEDDBCFR;


LEDDBCRR - dados sobre a duração do apagamento do LED.



when LEDDBCFR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0110";
		DAT_Bits(7)		<= '1'; --   () (disable/enable)
		DAT_Bits(6)		<= '0'; -- PWM Range Extend
		DAT_Bits(5)		<= '1'; --    
		DAT_Bits(4)		<= '0'; -- RESERVED
		DAT_Bits(3 downto 0)	<= "0011"; --   (0.5 )
		PWM_state_next		<= LEDDPWRR;


Os registros LEDDPWRR, LEDDPWRG e LEDDPWRB registram os dados sobre o brilho dos LEDs vermelho, azul e verde, respectivamente. O valor do brilho é calculado como uma porcentagem pela seguinte fórmula:



ADC(%) = PulseWidth/256


Portanto, diferentes valores de brilho dão uma mistura de cores, para que você possa brincar e alcançar o seu ideal.



when LEDDPWRR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0001";
		DAT_Bits(7 downto 0)	<= "00000001"; -- RED Pulse Width
		PWM_state_next		<= LEDDPWRG;


when LEDDPWRG =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0010";
		DAT_Bits(7 downto 0)	<= "11111111"; -- GREEN Pulse Width
		PWM_state_next		<= LEDDPWRB;


when LEDDPWRB =>
		led_en 			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0011";
		DAT_Bits(7 downto 0)	<= "00011111"; -- BLUE Pulse Width
		PWM_state_next		<= LEDDCR0;


Bem, o último registro LEDDCR0 registra as informações de ativação e os dois bits mais significativos da frequência do sinal de relógio PWM:



when LEDDCR0 =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1000";
		DAT_Bits(7)		<= '1'; --   ()
		DAT_Bits(6)		<= '1'; -- Flick Rate Select Bit (125/250 Hz)
		DAT_Bits(5)		<= '0'; --    (1/0)
		DAT_Bits(4) 		<= '0'; --      
		DAT_Bits(3)		<= '1'; -- Blinking Sequence Quick Stop Enable Bit
		DAT_Bits(2)		<= '0'; -- PWM Mode Selection Bit
		DAT_Bits(1 downto 0)	<= "10"; --    
		PWM_state_next		<= DONE;


Exemplos de implementação



RGB







Roxo / Branco







Resumindo



Bom, isso é tudo. Ao alterar os parâmetros, você pode obter um belo efeito de respiração do LED com diferentes cores e brilho, alterando os valores nos registros LEDDPWRR, LEDDPWRG, LEDDPWRB ou o valor atual do driver RGB. Abaixo estão links para o código no GitHub e toda a documentação necessária.



No futuro, pretendo testar outros pães e, se possível, colocá-los aqui para revisão.



Guia do usuário da placa de avaliação Código do

guia de uso do driver de LED iCE40




All Articles