Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Em primeira parte Eu tentei explicar a hobbyistas de eletrônica, que cresceram com projetos Arduino, como e por que eles deveriam ler datasheets e outra documentação sobre microcontroladores. O texto ficou extenso, então prometi mostrar exemplos práticos em um artigo separado. Então, vamos lá...

Hoje vou mostrar como usar datasheets para resolver tarefas bastante simples, mas necessárias para muitos projetos, com os controladores STM32 (Blue Pill) e STM8. Todos os projetos de demonstração são dedicados aos meus LEDs favoritos, que acenderemos em grandes quantidades, para o que teremos que usar uma variedade de periféricos interessantes.

O texto novamente ficou enorme, então para facilitar, vou fazer um índice:

STM32 Blue Pill: 16 LEDs com driver DM634
STM8: Configurando seis saídas PWM
STM8: 8 LEDs RGB em três pinos, interrupções

Isenção de responsabilidade: eu não sou engenheiro, não pretendo ter profundos conhecimentos em eletrônica; este artigo é destinado a outros amadores como eu. Na verdade, considerava como público-alvo eu mesmo de dois anos atrás. Se alguém me tivesse contado que ler datasheets para um chip desconhecido não é assustador, eu não teria levado tanto tempo para procurar pedaços de código na internet e inventar gambiarras com tesouras e band-aids.

O foco deste artigo são os datasheets, e não os projetos, portanto, o código pode não estar muito organizado e frequentemente ser improvisado. Os próprios projetos são bastante simples, embora sejam adequados para um primeiro contato com um novo chip.

Espero que meu artigo ajude alguém em uma fase semelhante de imersão no hobby.

STM32

16 LEDs com DM634 e SPI

Um pequeno projeto usando o Blue Pill (STM32F103C8T6) e o driver de LED DM634. Com os datasheets, vamos entender o driver, as portas IO da STM e configurar o SPI.

DM634

Um chip taiwanês com 16 saídas PWM de 16 bits, que pode ser conectado em cascata. O modelo de 12 bits é conhecido por um projeto local. Lightpack. Na época, ao escolher entre DM63x e o bem conhecido TLC5940, optei pelo DM por algumas razões: 1) o TLC no AliExpress é claramente falso, enquanto este não; 2) o DM possui PWM autônomo com seu próprio gerador de frequência; 3) eu podia comprá-lo barato em Moscou, em vez de esperar a encomenda do Ali. E, claro, eu estava curioso para aprender a controlar o chip por conta própria, em vez de usar uma biblioteca pronta. Agora, os chips são principalmente encontrados em um invólucro SSOP24, que não é difícil de soldar em um adaptador.

Como o fabricante é taiwanês, o datasheet o chip é descrito em inglês chinês, então isso será divertido. Primeiro, olhamos para o pinout (Pin Connection), para entender a que pino conectar cada coisa, e a descrição dos pinos (Descrição do Pino). 16 pinos:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Fontes de corrente contínua em entrada (drain aberto)

Sink / Saída open-drain – drain; fonte de corrente em entrada; saída, em estado ativo conectada à terra, – os LEDs se conectam ao driver pelos cátodos. Eletricidade, claro, nada mais é do que um 'drain aberto' (drain aberta), mas nas datasheets essa designação para pinos em modo drain é bastante comum.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Resistores externos entre REXT e GND para definir o valor da corrente de saída

Entre o pino REXT e a terra, é instalado um resistor de referência, que controla a resistência interna das saídas, veja o gráfico na página 9 da datasheet. No DM634, essa resistência também pode ser controlada programaticamente, ajustando o brilho global (global brightness); nesta artigo não entrarei em detalhes, apenas colocarei um resistor de 2.2 – 3 kOhm aqui.

Para entender como controlar o chip, vamos olhar a descrição da interface do dispositivo:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Ah, aqui está, o inglês chinês em toda sua glória. Traduzir isso é problemático, pode ser compreendido se desejado, mas há outro caminho – verificar como a conexão é descrita na datasheet do TLC5940, que é funcionalmente semelhante:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
… Para inserir dados no dispositivo, apenas três pinos são necessários. A borda de subida do sinal SCLK desloca os dados do pino SIN para o registro interno. Após todos os dados serem carregados, um curto sinal alto XLAT fixa os dados transmitidos sequencialmente nos registros internos. Os registros internos ativam quando o sinal XLAT está alto. Todos os dados são transmitidos com o bit mais significativo primeiro.

Latch – latch/fecho/fixador.
Borda de subida – borda de subida do pulso
MSB primeiro – com o bit mais significativo (mais à esquerda) para frente.
para clock de dados – transmitir dados sequencialmente (bit a bit).

A palavra trava é frequentemente encontrada na documentação de chips e é traduzida de várias formas, portanto, para melhor compreensão, permito-me

um pequeno esclarecimentoO driver LED – na verdade, um registrador de deslocamento. "Deslocamento" (deslocamento) no nome significa a movimentação de dados bit a bit dentro do dispositivo: cada novo bit inserido empurra toda a cadeia para frente. Uma vez que, durante o deslocamento, ninguém quer observar um piscar caótico de LEDs, o processo ocorre em registradores de buffer, separados do funcionamento por um obturador (trava) – é uma espécie de antecâmara, onde os bits se organizam na sequência correta. Quando tudo está pronto, o obturador se abre e os bits são enviados para trabalhar, substituindo o lote anterior. A palavra trava na documentação de microchips quase sempre implica tal obturador, independentemente das combinações em que é usada.

Assim, a transmissão de dados no DM634 é realizada da seguinte forma: configuramos a entrada DAI para o valor do bit mais significativo do LED distante, ativamos DCK para cima e para baixo; configuramos a entrada DAI para o valor do próximo bit, ativamos DCK; e assim por diante, até que todos os bits tenham sido transmitidos (clocked in), após o que ativamos o LAT. Isso pode ser feito manualmente (bit-bang), mas é melhor usar uma interface SPI especialmente projetada para isso, já que está disponível em nosso STM32 em duas instâncias.

A Blue Pill STM32F103

Introdução: os controladores STM32 são significativamente mais complexos que Atmega328, do que podem assustar. Entretanto, por questões de economia de energia, quase toda a periferia está desligada no início, e a frequência de clock é de 8 MHz de uma fonte interna. Felizmente, os programadores da STM escreveram um código que eleva o chip para os "calculados" 72 MHz, e os criadores de todos os IDEs que conheço incluíram isto no procedimento de inicialização, portanto, não precisamos configurar o clock (mas é possível se você realmente quiser). No entanto, teremos que ativar a periferia.

Documentação: na Blue Pill, está instalado o popular chip STM32F103C8T6, e existem dois documentos úteis sobre ele:

No datasheet, podem ser interessantes:

  • Pinouts – pinagens dos chips – no caso de decidirmos fazer as placas nós mesmos;
  • Mapa de Memória – um mapa de memória para um chip específico. No Manual de Referência, há um mapa para toda a linha, onde estão mencionados registros que não estão em nosso chip.
  • Tabela de Definições de Pinos – uma enumeração das funções principais e alternativas dos pinos; para a 'blue pill', pode-se encontrar imagens mais convenientes na internet com a lista de pinos e suas funções. Portanto, vamos imediatamente buscar 'Blue Pill pinout' e ter essa imagem em mãos:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Observação: na imagem da internet havia um erro, notado nos comentários, pelo qual agradecemos. A imagem foi substituída, mas isso é uma lição — informações não provenientes de datasheets devem ser sempre verificadas.

Removemos o datasheet, abrimos o Manual de Referência e a partir de agora usaremos apenas ele.
Ordem de ações: vamos nos familiarizar com a entrada/saída padrão, configurar o SPI e ativar a periferia necessária.

Entrada/Saída

Na Atmega328, a entrada/saída é implementada de forma extremamente simples, e a variedade de opções da STM32 pode ser confusa. Agora, precisamos apenas das saídas, mas mesmo elas têm quatro variantes:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
saída de coletor aberto, saída 'push-pull', 'push-pull' alternativo, coletor aberto alternativo

'Push-pull' (push-pull) – a saída habitual do Arduino, o pino pode assumir o valor de HIGH ou LOW. A situação com 'coletor aberto' é um pouco diferente, dificuldades, mas na verdade tudo é simples aqui:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Configuração da saída / quando a porta está designada como saída: / buffer de saída ativado: / – modo de coletor aberto: '0' no registro de saída ativa N-MOS, '1' no registro de saída deixa a porta em modo Hi-Z (P-MOS não é ativado) / – modo 'push-pull': '0' no registro de saída ativa N-MOS, '1' no registro de saída ativa P-MOS.

Toda a diferença entre coletor aberto (drain aberta) e 'push-pull' (push-pull) é que no primeiro, o pino não pode assumir o estado HIGH: ao gravar um '1' no registro de saída, ele entra em modo de alta impedância (high impedance, Hi-Z). Ao gravar um '0', o pino se comporta da mesma forma em ambos os modos, tanto logicamente quanto eletricamente.

No modo normal de saída, o pino simplesmente traduz o conteúdo do registro de saída. No modo 'alternativo', ele é controlado pela respectiva periferia (veja 9.1.4):

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Se o bit da porta estiver configurado como uma saída de função alternativa, o registro de saída é desativado e o pino é conectado ao sinal de saída da periferia.

A funcionalidade alternativa de cada pino está descrita em Definições de Pinos A ficha técnica está na imagem baixada. A nota de rodapé na ficha técnica responde à pergunta sobre o que fazer se um pino tiver várias funções alternativas.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Se vários blocos periféricos compartilharem um mesmo pino, para evitar conflito entre as funções alternativas, deve-se usar apenas um bloco periférico por vez, alternando com o bit de ativação de temporização do periférico (no registro RCC correspondente).

Além disso, os pinos em modo de saída têm também a velocidade de temporização. Esta é mais uma funcionalidade de economia de energia; neste caso, simplesmente definimos no máximo e esquecemos.

Então: estamos usando SPI, o que significa que dois pinos (dados e clock) devem ser ‘funções alternativas push-pull’, e outro (LAT) deve ser ‘push-pull normal’. Mas, antes de designá-los, vamos entender melhor o SPI.

SPI

Mais um breve esclarecimento.

SPI ou Serial Peripheral Interface (Interface Periférica Serial) é uma interface simples e muito eficiente para comunicação entre microcontroladores e o mundo externo. O seu funcionamento já foi descrito anteriormente, na parte sobre o driver LED chinês (no manual de referência, veja a seção 25). O SPI pode operar em modo mestre e escravo. Existem quatro canais básicos no SPI, dos quais nem todos precisam ser utilizados:

  • MOSI, Master Output / Slave Input: este pino, em modo mestre, envia dados, enquanto em modo escravo, recebe dados;
  • MISO, Master Input / Slave Output: inversamente, aceita dados em modo mestre e envia em modo escravo;
  • SCK, Serial Clock: define a frequência de transmissão de dados no modo mestre ou recebe o sinal de clock no modo escravo. Basicamente, marca os bits;
  • SS, Slave Select: com este canal, o escravo sabe que algo está sendo solicitado a ele. No STM32, é chamado de NSS, onde N = negativo, ou seja, o controlador torna-se escravo se este canal estiver em nível baixo. Funciona bem combinado com o modo Open Drain Output, mas isso é outra história.

Como tudo mais, o SPI no STM32 possui muitas funcionalidades, o que torna sua compreensão um pouco mais complicada. Por exemplo, ele pode operar tanto em SPI quanto em I2S, e as descrições na documentação estão misturadas, é necessário filtrar o que não se aplica. No nosso caso, a tarefa é bastante simples: apenas precisamos enviar dados, utilizando apenas MOSI e SCK. Vamos à seção 25.3.4 (comunicação half-duplex), onde encontramos 1 clock and 1 unidirectional data wire (1 sinal de clock e 1 fluxo de dados unidirecional):

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Neste modo, o aplicativo utiliza SPI, seja em modo de transmissão apenas, ou em modo de recepção apenas. / O modo de transmissão é similar ao modo duplex: os dados são transmitidos pelo pino de transmissão (MOSI em modo mestre ou MISO em modo escravo), e o pino de recepção (MISO ou MOSI, respectivamente) pode ser usado como um pino comum de entrada e saída. Nesse caso, o aplicativo pode simplesmente ignorar o buffer Rx (se for lido, não haverá dados transmitidos).

Ótimo, o pino MISO está livre, vamos conectar o sinal LAT a ele. Vamos resolver a questão do Slave Select, que no STM32 pode ser controlado programaticamente, o que é extremamente conveniente. Vamos ler o parágrafo intitulado na seção 25.3.1 Descrição Geral do SPI:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Controle programático do NSS (SSM = 1) / As informações sobre a seleção do escravo estão contidas no bit SSI do registrador SPI_CR1. O pino externo NSS permanece livre para outras necessidades do aplicativo.

É hora de escrever nos registradores. Decidi usar o SPI2, vamos procurar seu endereço base no datasheet – na seção 3.3 Mapa de Memória:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Então, vamos começar:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Abrimos a seção 25.3.3 com o título falante "Configuração do SPI em modo mestre":

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

1. Configure a frequência de clock da interface serial com os bits BR[2:0] no registrador SPI_CR1.

Os registradores estão agrupados na seção homônima do manual de referência. O deslocamento do endereço (Address offset) para o CR1 é 0x00, por padrão todos os bits estão zerados (Reset value 0x0000):

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Os bits BR ajustam o divisor da frequência de clock do controlador, definindo assim a frequência com a qual o SPI irá operar. A frequência do STM32 será de 72 MHz, o driver de LED, segundo seu datasheet, opera com frequência de até 25 MHz, portanto, deve-se dividir por quatro (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Configure os bits CPOL e CPHA para definir as relações entre a transmissão de dados e o clock da interface serial (veja o esquema na página 240).

Como estamos lendo o datasheet e não revisando os esquemas, vamos estudar a descrição textual dos bits CPOL e CPHA na página 704 (Descrição Geral do SPI):

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Fase e polaridade do sinal de clock
Com os bits CPOL e CPHA do registrador SPI_CR1, é possível escolher programaticamente quatro opções de relacionamentos de ritmo. O bit CPOL (polaridade do sinal de clock) controla o estado do sinal de clock quando os dados não estão sendo transmitidos. Este bit controla os modos mestre e escravo. Se CPOL estiver zerado, o pino SCK em repouso está em nível baixo. Se o bit CPOL estiver configurado, o pino SCK em repouso está em nível alto.
Se o bit CPHA (fase do sinal de clock) estiver definido, o segundo flanco do sinal SCK atua como um strobe de captura do bit mais significativo (descendente se CPOL estiver zerado, ou ascendente se CPOL estiver definido). Os dados são registrados na segunda transição do sinal de clock. Se o bit CPHA estiver zerado, o strobe de captura do bit mais significativo é o flanco frontal do sinal SCK (descendente se CPOL estiver definido, ou ascendente se CPOL estiver zerado). Os dados são capturados na primeira transição do sinal de clock.

Com essas informações, chegamos à conclusão de que ambos os bits devem permanecer zero, pois precisamos que o sinal SCK permaneça baixo quando não estiver em uso, e os dados sejam transmitidos no flanco frontal do pulso (veja Rising Edge no datasheet DM634).

A propósito, aqui encontramos pela primeira vez uma peculiaridade da lexicografia nos datasheets da ST: a frase "resetar um bit" é escrita to reset a bit, e não to clear a bit, como, por exemplo, na Atmega.

3. Defina o bit DFF para determinar o formato de bloco de dados de 8 bits ou 16 bits

Eu escolhi especificamente o DM634 de 16 bits, para não ter que me preocupar em transmitir dados de 12 bits PWM, como no DM633. Faz sentido definir DFF como um:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Configure o bit LSBFIRST no registro SPI_CR1 para determinar o formato do bloco

LSBFIRST, como o próprio nome diz, ajusta a transmissão com o bit menos significativo primeiro. Mas o DM634 espera receber os dados começando pelo bit mais significativo. Portanto, deixamos zerado.

5. No modo de hardware, se a entrada do pino NSS for necessária, aplique um sinal alto no pino NSS durante toda a sequência de transmissão de bytes. No modo de software, defina os bits SSM e SSI no registro SPI_CR1. Se o pino NSS deve funcionar como saída, apenas o bit SSOE precisa ser definido.

Definimos SSM e SSI para ignorar o modo de hardware NSS:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. Os bits MSTR e SPE devem ser definidos (eles permanecem definidos apenas se um sinal alto for aplicado ao NSS)

Na verdade, com esses bits, definimos nosso SPI como mestre e o ativamos:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI configurado, vamos imediatamente escrever as funções que enviam bytes ao driver. Continuamos a ler 25.3.3 "Configuração do SPI no modo mestre":

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Ordem de transmissão de dados
A transmissão começa quando um byte é gravado no buffer Tx.
O byte de dados é carregado no registrador de deslocamento em modo paralelo (da bus interna) durante a transmissão do primeiro bit, após o que é transmitido em modo (da barramento interno) durante a transmissão do primeiro bit, após o que é transmitido em sequência. no modo de pino MOSI, o primeiro ou último bit para frente, dependendo da configuração do bit LSBFIRST no registro CPI_CR1. O sinal TXE é configurado após a transmissão dos dados do buffer Tx para o registrador de deslocamento, e uma interrupção também é criada, se o bit TXEIE estiver configurado no registro CPI_CR1.

Destacamos algumas palavras na tradução para chamar a atenção para uma particularidade da implementação do SPI nos controladores STM. No Atmega, o sinal TXE (Tx Vazio, Tx está vazio e pronto para receber dados) é configurado apenas após o envio de todo o byte para fora. Aqui, esse sinal é configurado após o byte ter sido colocado no registrador de deslocamento interno. Como ele é inserido com todos os bits ao mesmo tempo (paralelamente), e os dados são transmitidos sequencialmente posteriormente, TXE é configurado antes que o byte seja completamente enviado. Isso é importante, pois, no caso do nosso driver de LED, precisamos acionar o pino LAT após a transmissão todas dos dados, ou seja, apenas o sinal TXE não é suficiente.

Isso significa que precisamos de outro sinal. Vamos consultar 25.3.7 – "Sinais de status":

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
<…>
Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Sinal BUSY
O sinal BSY é configurado e redefinido de forma automática (gravar nele não tem efeito). O sinal BSY indica o estado da camada de comunicação SPI.
Ele é redefinido:
quando a transmissão é concluída (exceto no modo mestre, se a transmissão for contínua)
quando o SPI é desligado
quando ocorre um erro no modo mestre (MODF=1)
Se a transmissão não for contínua, o sinal BSY é redefinido entre cada transmissão de dados.

Ok, será útil. Vamos descobrir onde está o buffer Tx. Para isso, lemos "Registro de Dados do SPI":

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Bits 15:0 DR[15:0] Registro de Dados
Dados recebidos ou dados para transmissão.
O registro de dados é dividido em dois buffers – um para gravação (buffer de transmissão) e outro para leitura (buffer de recepção). Gravar no registro de dados escreve no buffer Tx, e ler do registro de dados retornará o valor contido no buffer Rx.

E o registro de status, onde estão os sinais TXE e BSY:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Escrevemos:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Como precisamos transmitir 16 vezes dois bytes, pelo número de saídas do driver de LED, fica assim:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // finalizar transmissão

    LAT_pulse();
}

Mas ainda não conseguimos acionar o pino LAT, então vamos voltar para I/O.

Atribuindo pinos

Nos registradores do STM32F1, que correspondem ao estado dos pinos, são bastante incomuns. É claro que existem mais deles do que nos Atmega, mas eles também diferem de outros chips STM. Seção 9.1 Descrição Geral do GPIO:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Cada uma das portas de entrada/saída de uso geral (GPIO) possui dois registradores de configuração de 32 bits (GPIOx_CRL e GPIOx_CRH), dois registradores de dados de 32 bits (GPIOx_IDR e GPIOx_ODR), um registrador de configuração/reset de 32 bits (GPIOx_BSRR), um registrador de reset de 16 bits (GPIOx_BRR) e um registrador de bloqueio de 32 bits (GPIOx_LCKR).

Eles são incomuns e um pouco inconvenientes, especialmente os dois primeiros registradores, porque os 16 pinos da porta estão dispersos entre eles em um formato de "quatro bits por vez". Ou seja, os pinos do zero ao sétimo estão no CRL e os demais no CRH. Ao mesmo tempo, os outros registradores acomodam os bits de todos os pinos da porta – muitas vezes permanecendo pela metade "reservados".

Para simplificar, começaremos pelo final da lista.

Não precisaremos do registrador de bloqueio.

Os registradores de configuração e reset são curiosos, pois duplicam parcialmente uns aos outros: pode-se escrever tudo apenas no BSRR, onde os 16 bits mais significativos resetarão o pino para zero, enquanto os menos significativos o configurarão para 1, ou também usar o BRR, cujos 16 bits menos significativos resetam apenas o pino. Eu prefiro a segunda opção. Esses registradores são importantes porque garantem acesso atômico aos pinos:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Configuração ou reset atômico
Não é necessário desativar as interrupções ao programar o GPIOx_ODR a nível de bit: é possível alterar um ou vários bits em uma única operação atômica de gravação APB2. Isso é alcançado escrevendo "1" no registrador de configuração/reset (GPIOx_BSRR ou, apenas para reset, no GPIOx_BRR) do bit que precisa ser alterado. Os outros bits permanecerão inalterados.

Os registradores de dados têm nomes bastante sugestivos – IDR = Input Registrador de Direção, registrador de entrada; ODR = Saída Registrador de Direção, registrador de saída. Não precisamos deles no projeto atual.

E, finalmente, os registradores de controle. Como estamos interessados nos pinos do segundo SPI, especificamente PB13, PB14 e PB15, vamos direto para o CRH:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

E vemos que teremos que escrever algo nos bits de 20 a 31.

Já discutimos anteriormente o que desejamos dos pinos, portanto, aqui não vou incluir uma captura de tela, apenas direi que o MODE define a direção (entrada, se ambos os bits estiverem em 0) e a velocidade do pino (precisamos de 50MHz, ou seja, ambos os pinos em '1'), enquanto o CNF define o modo: comum 'push-pull' – 00, 'alternativo' – 10. Por padrão, como vimos acima, todos os pinos têm o terceiro bit de baixo (CNF0) configurado, que os coloca em modo. entrada flutuante.

Como planejo fazer mais coisas com este chip, para simplificar, defini todos os possíveis valores de MODE e CNF tanto para os registradores de controle inferiores quanto superiores.

Então, é assim que funciona.

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

Nossos pinos estão no porto B (endereço base – 0x40010C00), código:

#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))

#define _BRR  0x14
#define _BSRR 0x10
#define _CRL  0x00
#define _CRH  0x04

//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14

//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);

//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;

//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;

E, consequentemente, podemos escrever definições para LAT, que serão acionadas pelos registradores BRR e BSRR:

/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)

#define LAT_low() _PORTB_(_BRR) = (1<<14)

(LAT_low é apenas por inércia, sempre foi assim, então vai continuar)

Agora tudo está ótimo, exceto que não funciona. Porque é STM32, aqui economiza-se eletricidade, então precisamos ativar os clocks da periferia necessária.

Ativamos o clock

O clock é controlado pelos relógios, também conhecidos como Clock. Já notamos a abreviatura RCC. Procuramos na documentação: é Reset and Clock Control (Controle de Reset e Clock).

Como mencionado acima, felizmente, a parte mais complexa do tema de clock foi tratada pela equipe da STM, a quem agradecemos muito (mais uma vez, vou incluir o link para o site do Di Halt, para ilustrar o quão complicado isso pode ser). Precisamos apenas dos registradores que controlam a ativação do clock da periferia (Registradores de Habilitação do Clock da Periferia). Para começar, vamos encontrar o endereço base do RCC, está no início do "Mapa de Memória":

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

Depois disso, podemos clicar no link, tentando encontrar algo na tabela, ou, muito melhor, percorrer as descrições dos registradores de habilitação das seções sobre registradores de habilitação. Onde encontraremos o RCC_APB1ENR e o RCC_APB2ENR:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

E neles, respectivamente, os bits que ativam o clock SPI2, IOPB (Porto I/O B) e funções alternativas (AFIO).

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

O código final pode ser encontrado aqui.

Se houver oportunidade e desejo de testar, conectamos o DM634 da seguinte forma: DAI ao PB15, DCK ao PB13, LAT ao PB14. Alimentamos o driver com 5 volts, sem esquecer de conectar os terras.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

STM8 PWM

PWM no STM8

Quando planejei este artigo, decidi tentar dominar algum funcionalidade de um chip desconhecido apenas com o datasheet, para que eu não me tornasse um sapateiro sem sapatos. O STM8 se encaixava perfeitamente nesse papel: primeiro, eu tinha algumas placas chinesas com STM8S103, e segundo, ele não é muito popular, portanto a tentação de procurar e encontrar uma solução na internet esbarrava na ausência dessas soluções.

O chip também possui o datasheet e manual de referência RM0016, no primeiro a pinagem e endereços dos registradores, no segundo – tudo o mais. O STM8 é programado em C numa IDE feia chamada ST Visual Develop.

Clock e I/O

Por padrão, o STM8 opera a uma frequência de 2 MHz, isso precisa ser corrigido imediatamente.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
O sinal de clock HSI (High-Speed Internal)
O sinal de clock HSI é gerado a partir de um oscilador RC interno de 16 MHz com divisor programável (de 1 a 8). Ele é definido no registrador de divisor de clock (CLK_CKDIVR).
Nota: no início, a fonte principal do sinal de clock é o gerador RC HSI com divisor 8.

Encontramos o endereço do registrador no datasheet, a descrição no refman e vimos que o registrador precisa ser limpo:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Como vamos ativar PWM e conectar LEDs, olhamos a pinagem:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

O chip é pequeno, muitas funções estão mapeadas para os mesmos pinos. O que está entre colchetes é a "funcionalidade alternativa", que é alternada por "bytes de opções" (option bytes) – algo como os fusíveis do Atmega. Seus valores podem ser alterados programaticamente, mas não é necessário, pois a nova funcionalidade é ativada apenas após um reinício. É mais fácil usar o ST Visual Programmer (que vem com o Visual Develop), que pode modificar esses bytes. Na pinagem, vê-se que as saídas CH1 e CH2 do primeiro timer estão escondidas entre colchetes; precisamos marcar os bits AFR1 e AFR0 no STVP, onde o segundo também moverá a saída CH1 do segundo timer de PD4 para PC5.

Assim, os LEDs serão controlados por 6 pinos: PC6, PC7 e PC3 para o primeiro timer, PC5, PD3 e PA3 para o segundo.

A configuração dos próprios pinos I/O no STM8 é mais simples e lógica do que no STM32:

  • o familiar registrador de direção de dados DDR (Data Direction Register): 1 = saída;
  • o primeiro registrador de controle CR1 ao definir saída determina o modo "push-pull" (1) ou coletor aberto (0); como estou conectando LEDs ao chip pelos cátodos, deixo zeros aqui;
  • o segundo registrador de controle CR2 ao definir saída determina a velocidade de clock: 1 = 10 MHz

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

Configuração de PWM

Para começarmos, vamos definir os termos:

  • Frequência PWM – a frequência com que o temporizador conta;
  • Auto-reload, AR – valor de recarga automática até o qual o temporizador contará (período do impulso);
  • Update Event, UEV – evento que ocorre quando o temporizador atinge o AR;
  • Ciclo de Trabalho PWM – fator de preenchimento do PWM, frequentemente chamado de "duty cycle";
  • Valor de Captura/Comparação – valor para captura/comparação, até o qual o temporizador fará algo (no caso de PWM – inverterá o sinal de saída);
  • Valor de Pré-carregamento – valor pré-carregado. O valor de comparação não pode ser alterado enquanto o temporizador está contando, caso contrário, o ciclo PWM será interrompido. Portanto, os novos valores transmitidos são colocados em um buffer e retirados dele quando o temporizador atinge o final da contagem e é reiniciado;
  • Modos alinhados à borda e Modos centralizados – alinhamento à borda e no centro, o que corresponde ao modo Atmel PWM Rápido e PWM Fase-Corrigida.
  • OCiREF, Sinal de Referência de Comparação de Saída – sinal de saída de referência, que, no modo PWM, é aplicado ao pino correspondente.

Como já é claro da pinagem, existem capacidades de PWM em dois temporizadores – o primeiro e o segundo. Ambos são de 16 bits, o primeiro possui uma série de recursos adicionais (em particular, pode contar para cima e para baixo). Precisamos que ambos funcionem da mesma forma, então decidi começar pelo segundo que é deliberadamente mais simples, para não usar acidentalmente algo que ele não possui. Um problema é que a descrição da funcionalidade PWM de todos os temporizadores no manual de referências está no capítulo sobre o primeiro temporizador (17.5.7 Modo PWM), portanto, é necessário ficar alternando entre as seções do documento.

O PWM no STM8 tem uma importante vantagem sobre o PWM da Atmega:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
PWM com alinhamento à borda
Configuração de contagem para cima
A contagem para cima está ativa se o bit DIR no registro TIM_CR1 estiver zerado.
Exemplo
O exemplo usa o primeiro modo de PWM. O sinal de referência de PWM OCiREF é mantido em alto enquanto TIM1_CNT < TIM1_CCRi. Caso contrário, ele assume o nível baixo. Se o valor para comparação no registro TIM1_CCRi for maior do que o valor de recarga automática (registro TIM1_ARR), o sinal OCiREF é mantido em 1. Se o valor para comparação for 0, OCiREF é mantido em zero.…

O temporizador STM8 durante o evento de atualização primeiro verifica o valor de comparação, e só então emite o sinal de referência. No Atmega, o temporizador primeiro conta e depois compara, resultando em uma situação onde, ao o valor de comparação == 0 No final, obtemos uma agulha com a qual temos que lidar (por exemplo, invertendo logicamente em software).

Então, o que queremos fazer: PWM de 8 bits (AR == 255), contamos de baixo para cima, alinhamento por limite. Como as lâmpadas estão conectadas ao chip pelos cátodos, o PWM deve emitir 0 (LED ligado) até o valor de comparação e 1 depois.

Já lemos sobre alguns modos PWM, portanto, encontramos o registro necessário do segundo temporizador pesquisando no manual de referência por esta frase (18.6.8 – TIMx_CCMR1):

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
110: Primeiro modo PWM – ao contar de baixo para cima, o primeiro canal está ativo enquanto TIMx_CNT < TIMx_CCR1. Caso contrário, o primeiro canal está inativo. [mais adiante no documento, cópia errada do temporizador 1]
111: Segundo modo PWM – ao contar de baixo para cima, o primeiro canal está inativo enquanto TIMx_CNT < TIMx_CCR1. Caso contrário, o primeiro canal está ativo.

Como os LEDs estão conectados ao microcontrolador pelos cátodos, o segundo modo nos convém (o primeiro também, mas ainda não sabemos disso).

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Bit 3 OC1PE: Habilitar pré-carga da saída 1
0: O registro de pré-carga em TIMx_CCR1 está desativado. É possível escrever em TIMx_CCR1 a qualquer momento. O novo valor é aplicado imediatamente.
1: O registro de pré-carga em TIMx_CCR1 está ativado. As operações de leitura/escrita se referem ao registro de pré-carga. O valor pré-carregado de TIMx_CCR1 é carregado no registro de sombra a cada evento de atualização.
*Observação: Para o funcionamento correto do modo PWM, os registros de pré-carga devem estar ativados. Isso não é necessário em modo de sinal único (onde o bit OPM está definido no registro TIMx_CR1).

Ok, ativamos tudo o que precisamos para os três canais do segundo temporizador:

#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309

#define PWM_MODE2   0x70 //PWM mode 2, 0b01110000
#define OCxPE       0x08 //preload enable

TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);

AR é composto por dois registros de 8 bits, aqui é simples:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

O segundo temporizador só consegue contar de baixo para cima, alinhamento por limite, não é necessário mudar nada. Vamos definir o divisor de frequência, por exemplo, 256. O divisor do segundo temporizador é definido no registro TIM2_PSCR e representa uma potência de dois:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Agora só falta ativar as saídas e o próprio segundo temporizador. A primeira tarefa é resolvida pelos registros Capture/Compare Habilitar: são dois, com três canais distribuídos de forma assimétrica. Aqui também podemos saber que é possível alterar a polaridade do sinal, ou seja, em princípio, poderíamos usar o Modo PWM 1. Escrevemos:

#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B

#define CC1E  (1<<0) // CCER1
#define CC2E  (1<<4) // CCER1
#define CC3E  (1<<0) // CCER2

TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;

E, finalmente, ativamos o temporizador no registro TIMx_CR1:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Vamos escrever uma versão simples do AnalogWrite(), que transmitirá valores para o temporizador para comparação. Os registros, previsivelmente, são chamados de registros Capture/Compare, dois para cada canal: os 8 bits inferiores em TIM2_CCRxL e os superiores em TIM2_CCRxH. Como estamos utilizando PWM de 8 bits, basta escrever apenas os bits inferiores:

#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316

void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
    TIM2_CCR1L = r;
    TIM2_CCR2L = g;
    TIM2_CCR3L = b;
}

O leitor atento notará que obtivemos um PWM um pouco defeituoso, incapaz de fornecer 100% de preenchimento (com o valor máximo de 255, o sinal é invertido em um ciclo do temporizador). Para LEDs, isso não faz diferença, e o leitor subjetivo já deve ter uma ideia de como corrigir isso.

O PWM no segundo temporizador está funcionando, vamos para o primeiro.

O primeiro temporizador possui exatamente os mesmos bits nos mesmos registradores (apenas os bits que estavam 'reservados' no segundo temporizador, no primeiro são usados ativamente para várias funcionalidades avançadas). Portanto, basta encontrar os endereços desses mesmos registradores no datasheet e copiar o código. E também mudar o valor do divisor de frequência, já que o primeiro temporizador quer um valor de 16 bits exato em dois registradores, em vez de uma potência de dois. Divisor Alto e Baixo. Tudo o que fazemos e… o primeiro temporizador não funciona. Qual é o problema?

O problema pode ser resolvido apenas revisando toda a seção sobre os registradores de controle do temporizador 1, onde procuramos aquele que não está no segundo temporizador. Seria encontrado 17.7.30 Registrador de interrupção (TIM1_BKR), onde há um bit assim:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Ativar a saída principal

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Agora sim, tudo certo, o código lá mesmo.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

STM8 Multiplex

Multiplexação no STM8

O terceiro mini-projeto consiste em conectar oito LEDs RGB ao segundo temporizador em modo PWM e fazê-los mostrar diferentes cores. A base é o conceito de multiplexação de LED, que consiste em que, se acendermos e apagarmos os LEDs muito, muito rapidamente, parecerá que estão sempre acesos (persistência da visão, inércia da percepção visual). Uma vez, fiz algo assim no Arduino..

O algoritmo funciona assim:

  • conectamos o ânodo do primeiro LED RGB;
  • o acendemos, aplicando os sinais corretos nos cátodos;
  • aguardamos o final do ciclo PWM;
  • conectamos o ânodo do segundo LED RGB;
  • o acendemos…

E assim por diante. Claro, para um funcionamento adequado, é necessário que a conexão do ânodo e o 'acendimento' do LED ocorram simultaneamente. Bem, ou quase. De qualquer forma, precisamos escrever um código que no três canais do segundo temporizador forneça valores, ao atingir UEV mudá-los e ao mesmo tempo alterar o LED RGB ativo no momento.

Como a mudança de LED é feita automaticamente, é necessário criar uma "memória de vídeo", de onde o manipulador de interrupção obterá os dados. Trata-se de um simples array:

uint8_t cores[8][3];

Para mudar a cor de um LED específico, basta escrever os valores desejados neste array. E a variável responsável pelo número do LED ativo será

uint8_t cnt;

Demux

Para uma multiplexação correta, precisaremos, estranhamente, do demultiplexador CD74HC238. O demultiplexador é um chip que implementa o operador <<. Através de três pinos de entrada (bits 0, 1 e 2), alimentamos com um número de três bits X, e ele ativa a saída número (1<<X). As outras entradas do chip são usadas para escalar toda a construção. Precisamos desse chip não apenas para reduzir o número de pinos ocupados do microcontrolador, mas também por segurança – para não ativar acidentalmente mais LEDs do que o permitido e não queimar o MC. O chip é muito barato e deve ser mantido sempre em casa.

O CD74HC238 será responsável por fornecer tensão ao anodo do LED necessário. Em uma multiplexação completa, ele forneceria tensão à coluna através de um P-MOSFET, mas nesta demonstração pode ser feito diretamente, pois ele consome 20 mA, de acordo com classificações máximas absolutas o datasheet. Do datasheet do CD74HC238 precisamos do pinout e desta colinha:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
H = nível alto de tensão, L = nível baixo de tensão, X – tanto faz.

Conectamos E2 e E1 ao terra, E3, A0, A1 e A3 aos pinos PD5, PC3, PC4 e PC5 do STM8. Como a tabela acima contém níveis baixos e altos, configuramos esses pinos como saídas push-pull.

PWM

O PWM no segundo timer é configurado da mesma forma que na história anterior, com duas exceções:

Primeiro, devemos habilitar a interrupção no Evento de Atualização (UEV), que chamará a função que alterna o LED ativo. Isso é feito alterando o bit Habilitar Interrupção de Atualização no registro com o nome sugestivo

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
Registro de Habilitação de Interrupções

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

A segunda diferença está relacionada a um fenômeno de multiplexação conhecido como ghosting – iluminação parasitária de diodos. No nosso caso, isso pode ocorrer devido ao fato de que o temporizador, ao acionar a interrupção no UEV, continua contando, e o manipulador de interrupção não consegue desligar o LED antes que o temporizador já comece a escrever algo nas saídas. Para combater isso, será necessário inverter a lógica (0 = brilho máximo, 255 = nada aceso) e evitar valores extremos de ciclo de trabalho. Ou seja, garantir que após o UEV os diodos se apaguem completamente por um ciclo de PWM.

Mudamos a polaridade:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

Evitamos definir r, g e b em 255 e não esquecemos de invertê-los durante o uso.

Interrupções

A essência da interrupção é que, sob certas circunstâncias, o chip para de executar o programa principal e aciona alguma função externa. As interrupções ocorrem devido a impactos externos ou internos, incluindo o temporizador.

Quando criamos o projeto pela primeira vez no ST Visual Develop, além de main.c , recebemos uma janela com um arquivo enigmático stm8_interrupt_vector.c, automaticamente incluído no projeto. Neste arquivo, uma função está vinculada a cada interrupção NonHandledInterrupt. Precisamos vincular nossa função à interrupção necessária.

No datasheet, há uma tabela de vetores de interrupção, onde encontramos o necessário:

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8
13 TIM2 atualização/transbordo
14 TIM2 captura/comparação

Precisamos alterar o LED na UEV, então precisamos da interrupção nº 13.

Assim, em primeiro lugar, no arquivo stm8_interrupt_vector.c , mudamos o nome da função que responde pela interrupção nº 13 (IRQ13) para o nosso:

{0x82, TIM2_Overflow}, /* irq13 */

Em segundo lugar, teremos que criar um arquivo main.h com este conteúdo:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

E por fim, escrever essa função em seu main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // desligamos o desmultiplexador
    PC_ODR = (cnt<<3); // registramos um novo valor no desmultiplexador
    PD_ODR |= (1<<5); // ligamos o desmultiplexador

    TIM2_SR1 = 0; // resetamos o sinal de Interrupção de Atualização Pendência

    cnt++; 
    cnt &= 7; // movemos o contador LED

    TIM2_CCR1L = ~colors[cnt][0]; // transferimos os valores invertidos para o buffer
    TIM2_CCR2L = ~colors[cnt][1]; // para o próximo ciclo de PWM
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Agora, precisamos habilitar as interrupções. Isso é feito com um comando de assembly rim – você terá que procurá-lo em Manual de Programação:

//enable interrupts
_asm("rim");

Outro comando de assembly – sim – desativa as interrupções. Elas devem ser desligadas durante a gravação de novos valores na 'memória de vídeo', para que uma interrupção acionada em um momento inadequado não danifique o array.

Todo o código – no GitHub.

Lendo datasheets 2: SPI no STM32; PWM, temporizadores e interrupções no STM8

Se essa artigo for útil para alguém, então não escrevi em vão. Fico feliz em receber comentários e observações, tentarei responder a todos.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster