Lemos as fichas técnicas 2: SPI no STM32; Temporizadores PWM e interrupções no STM8


Na primeira parte, tentei dizer aos entusiastas eletrônicos que cresceram fora das calças do Arduino como e por que eles deveriam ler folhas de dados e outras documentações para microcontroladores. Como o texto era grande, prometi mostrar exemplos práticos em um artigo separado. Bem, ele se chamava de carga ...


Hoje vou mostrar como usar folhas de dados para resolver tarefas bastante simples, mas necessárias para muitos projetos, nos controladores STM32 (Blue Pill) e STM8. Todos os projetos de demonstração são dedicados aos meus LEDs favoritos, vamos acendê-los em grandes quantidades, para os quais teremos que usar todos os tipos de periféricos interessantes.


O texto acabou sendo enorme novamente, por conveniência, eu estou fazendo o conteúdo:


Pílula azul STM32: 16 LEDs com driver DM634
STM8: Configurando Seis Pinos PWM
STM8: 8 LEDs RGB em três pinos, interrompe


Disclaimer: Eu não sou engenheiro, não pretendo ter um profundo conhecimento em eletrônica, o artigo é destinado a amantes como eu. De fato, como público-alvo, eu me considerava dois anos atrás. Se alguém me dissesse que não é assustador ler fichas técnicas em um chip desconhecido, eu não gastaria muito tempo pesquisando alguns códigos na Internet e inventando muletas com tesoura e um curativo.


No centro deste artigo, estão as planilhas de dados, não os projetos; portanto, o código pode não ser muito penteado e frequentemente triturado. Os projetos em si são muito simples, embora adequados para o primeiro contato com o novo chip.


Espero que meu artigo ajude alguém em um estágio semelhante em um mergulho por hobby.


STM32


16 LEDs com DM634 e SPI


Um pequeno projeto usando o Blue Pill (STM32F103C8T6) e o driver de LED DM634. Com a ajuda das folhas de dados, lidaremos com o driver, portas IO STM e configuraremos o SPI.


DM634


O chip de Taiwan com 16 saídas PWM de 16 bits, pode ser conectado em cadeias. O modelo mais jovem de 12 bits é conhecido pelo projeto doméstico Lightpack . Ao mesmo tempo, escolhendo entre o DM63x e o conhecido TLC5940, parei no DM por vários motivos: 1) O TLC no Aliexpress é definitivamente falso, mas esse não é; 2) o DM possui um PWM autônomo com seu próprio gerador de frequência; 3) poderia ser comprado mais barato em Moscou, e não esperar um pacote com Ali. Bem, é claro, foi interessante aprender a gerenciar o chip sozinho e não usar uma biblioteca pronta. Os chips agora são apresentados principalmente no pacote SSOP24, e são fáceis de soldar no adaptador.


Como o fabricante é taiwanês, a folha de dados do chip está escrita em inglês chinês, o que significa que será divertido. Primeiro, observe a conexão dos pinos ( conexão dos pinos ) para entender a qual perna conectar e a descrição dos pinos (descrição dos pinos). 16 conclusões:



Fontes de entrada de corrente contínua (dreno aberto)


Saída de pia / dreno aberto - dreno; fonte de corrente de entrada; saída ativa conectada ao terra - os LEDs são conectados ao driver por cátodos. Eletricamente, é claro que isso não é um "dreno aberto", mas nas planilhas de dados essa designação para saídas no modo dreno é comum.



Resistores externos entre REXT e GND para definir o valor da corrente de saída


Um resistor de referência é instalado entre o pino REXT e o terra, que controla a resistência interna das saídas, veja o gráfico na página 9 da folha de dados. No DM634, essa resistência também pode ser controlada programaticamente, definindo o brilho global ; Não vou entrar em detalhes neste artigo, apenas coloquei um resistor de 2,2 - 3 kOhm aqui.


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



Sim, aqui está, inglês chinês em toda a sua glória. É problemático traduzir, você pode entendê-lo, se desejar, mas há outra maneira - observar como a conexão é descrita na folha de dados para o TLC5940 funcionalmente próximo:



... Apenas três pinos são necessários para inserir dados no dispositivo. A borda principal do sinal SCLK muda os dados do pino SIN para o registro interno. Após o download de todos os dados, um pequeno sinal XLAT alto captura os dados seriais nos registros internos. Registradores internos - válvulas de acionamento XLAT. Todos os dados são transmitidos no bit mais significativo para a frente.


Trava - uma trava / trava / braçadeira.
Borda ascendente - borda principal do impulso
MSB primeiro - o bit mais significativo (mais à esquerda) para a frente.
para registrar dados - transmita dados sequencialmente (bit a bit).


A palavra trava é frequentemente encontrada na documentação para chips e é traduzida de várias maneiras, por isso me permitirei entender

pequeno programa educacional
O driver de LED é essencialmente um registro de turno. “Shift” no nome é um movimento de dados bit a bit dentro do dispositivo: cada novo bit empurrado para dentro empurra toda a cadeia à sua frente. Como ninguém quer observar o caótico piscar dos LEDs durante o turno, o processo ocorre em registros de buffer separados dos de trabalho por uma trava - esse é um tipo de sala de espera onde os bits são organizados na seqüência desejada. Quando tudo estiver pronto, o obturador se abre e os bits são enviados para o trabalho, substituindo o lote anterior. A palavra trava na documentação para microcircuitos quase sempre implica em um amortecedor, independentemente da combinação usada.

Portanto, a transferência de dados para o DM634 é a seguinte: defina a entrada DAI no bit de ordem superior do LED remoto, puxe o DCK para cima e para baixo; defina a entrada DAI para o próximo bit, puxe DCK; e assim por diante, até que todos os bits sejam transmitidos ( cronometrados ), após o que puxamos o LAT. Isso pode ser feito manualmente ( bit-bang ), mas é melhor usar a interface SPI afiada especialmente para isso, pois ela é apresentada em nosso STM32 em duas cópias.


Blue Tablet STM32F103


Introdutório: Os controladores STM32 são muito mais complicados que o Atmega328 do que podem assustar. Ao mesmo tempo, por razões de economia de energia, quase todos os periféricos são desativados no início e a frequência do relógio é de 8 MHz a partir de uma fonte interna. Felizmente, os programadores da STM escreveram o código que levou o chip aos 72 MHz "calculados" e os autores de todos os IDEs que eu conhecia o incluíram no procedimento de inicialização, portanto, não precisamos cronometrar (mas você pode, se realmente quiser ). Mas você precisa ligar os periféricos.


Documentação: O popular chip STM32F103C8T6 está instalado no Blue Pill, existem dois documentos úteis para ele:



Em uma folha de dados, podemos estar interessados ​​em:


  • Pinagens - pinagens de batatas fritas - no caso de decidirmos fazer pranchas;
  • Mapa da memória - cartão de memória para um chip específico. No Manual de Referência, há um cartão para toda a linha, que lista os registros que não estão no nosso.
  • Definições dos pinos da tabela - listando as funções principais e alternativas dos pinos; para a “pílula azul” na Internet, você pode encontrar fotos mais convenientes com uma lista de pinos e suas funções. Portanto, pesquise imediatamente a pinagem do Blue Pill no Google e mantenha esta foto à mão:


Nota: na imagem da Internet, ocorreu um erro nos comentários, pelo qual obrigado. A imagem foi substituída, mas esta é uma lição - é melhor verificar informações de folhas que não são de dados.


Removemos a folha de dados, abrimos o Manual de Referência, agora apenas a usamos.
Procedimento: lidamos com entrada / saída padrão, configuramos o SPI, ligamos os periféricos desejados.


Entrada-saída


A E / S do Atmega328 é extremamente simples, e é por isso que a abundância de opções do STM32 pode ser confusa. Agora, precisamos apenas de conclusões, mas mesmo existem quatro opções:



saída de dreno aberto, saída push-pull, saída push-pull alternativa, dreno aberto alternativo


Push-pull ” ( push-pull ) - a conclusão usual da Arduina, o pino pode ser ALTO ou BAIXO. Mas com o "dreno aberto" existem dificuldades , embora de fato tudo seja simples aqui:




Configuração de saída / quando a porta é atribuída à saída: / buffer de saída está ativado: / - modo de dreno aberto: “0” ativa N-MOS no registro de saída, “1” deixa a porta no modo Hi-Z no registro de saída (o P-MOS não está ativado ) / - modo push-pull: “0” no registro de saída ativa o N-MOS, “1” no registro de saída ativa o P-MOS.


Toda a diferença entre dreno aberto e push-pull é que, no primeiro pino, ele não pode aceitar o estado ALTO: ao gravar uma unidade no registro de saída, ele muda para alta impedância -Z ). Ao gravar zero, o pino nos dois modos se comporta da mesma maneira, tanto lógica quanto eletricamente.


No modo de saída normal, o pino simplesmente traduz o conteúdo do registro de saída. Na "alternativa" é controlado pela periferia correspondente (ver 9.1.4):



Se o bit da porta estiver configurado como uma saída de função alternativa, o registro de saída será desativado e o pino será conectado ao sinal de saída periférico.


Uma funcionalidade alternativa para cada pino é descrita na folha de dados das Definições de pinos e está na imagem baixada. Quando perguntado o que fazer se o pino tiver várias funções alternativas, a resposta fornece uma nota de rodapé na folha de dados:



Se vários blocos periféricos usarem o mesmo pino, para evitar conflitos entre funções alternativas, apenas um bloco periférico deve ser usado ao mesmo tempo, alternando usando o bit de ativação do relógio periférico (no registro RCC correspondente).


Finalmente, os pinos no modo de saída também têm uma velocidade de clock. Esse é outro recurso de economia de energia; no nosso caso, basta configurá-lo ao máximo e esquecê-lo.


Então, como usamos SPI, dois pinos (com dados e um sinal de relógio) devem ser uma "função alternativa de pressão" e outro (LAT) deve ser "pressão normal". Mas antes de atribuí-los, lidaremos com o SPI.


SPI


Outro pequeno programa educacional

SPI ou Serial Peripherial Interface (interface periférica serial) - uma interface simples e muito eficaz para comunicação de MK com outros MK e o mundo exterior em geral. O princípio de sua operação já foi descrito acima, sobre o driver de LED chinês (no manual de referência, consulte a seção 25). O SPI pode operar nos modos mestre ("mestre") e escravo ("escravo"). O SPI possui quatro canais básicos, dos quais nem todos podem estar envolvidos:


  • MOSI, Saída Mestre / Entrada Escrava: este pino no modo mestre envia, mas no modo escravo recebe dados;
  • MISO, Master Input / Slave Output: pelo contrário, no mestre aceita, no escravo - dá;
  • SCK, Serial Clock: define a frequência da transferência de dados no mestre ou recebe um sinal de relógio no escravo. Bate essencialmente os bits;
  • SS, Slave Select: através deste canal, o escravo aprende que quer algo dele. No STM32, é chamado NSS, onde N = negativo, ou seja, o controlador se torna escravo se houver terra neste canal. Combina bem com o modo Open Drain Output, mas essa é uma história diferente.

Como todo o resto, o SPI no STM32 é rico em funcionalidades, o que dificulta a compreensão. Por exemplo, ele pode funcionar não apenas com o SPI, mas também com a interface I2S e, na documentação em que suas descrições são misturadas, é necessário cortar o excesso em tempo hábil. Nossa tarefa é extremamente simples: você só precisa enviar dados usando apenas MOSI e SCK. Vamos para a seção 25.3.4 (comunicação half-duplex), onde encontramos 1 relógio e 1 fio de dados unidirecional (1 sinal de relógio e 1 fluxo de dados unidirecional):



Nesse modo, o aplicativo usa o SPI no modo somente transmissão ou somente recebimento. / O modo somente transmissão é semelhante ao modo duplex: os dados são transmitidos no pino transmissor (MOSI no modo mestre ou MISO no modo escravo), e o pino receptor (MISO ou MOSI, respectivamente) pode ser usado como um pino de entrada / saída regular. Nesse caso, é suficiente que o aplicativo ignore o buffer Rx (se você o ler, não haverá dados transmitidos).


Bem, o pino MISO está livre de nós, vamos conectar o sinal LAT a ele. Lidamos com o Slave Select, que pode ser controlado programaticamente no STM32, o que é extremamente conveniente. Lemos o parágrafo com o mesmo nome na seção 25.3.1 da Descrição Geral da SPI:



Controle de programa NSS (SSM = 1) / Informações sobre a seleção do escravo estão contidas no bit SSI do registro SPI_CR1. O pino NSS externo permanece livre para outras necessidades do aplicativo.


É hora de escrever nos registros. Decidi usar o SPI2, estamos procurando o endereço base na folha de dados - na seção 3.3 Mapa de Memória:



Bem, começamos:


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

Abrimos a seção 25.3.3 com o ditado "Configurando SPI no modo mestre":



1. Defina a velocidade do relógio serial com os bits BR [2: 0] no registro SPI_CR1.


Os registros são coletados na seção do manual de referência com o mesmo nome. CR1 tem um deslocamento de endereço ( deslocamento de endereço ) de 0x00; por padrão, todos os bits são redefinidos ( valor de redefinição 0x0000):



Os bits BR definem o divisor de frequência do relógio do controlador, determinando assim a frequência na qual o SPI irá operar. Teremos uma frequência de STM32 de 72 MHz, o driver de LED, de acordo com sua folha de dados, trabalha com uma frequência de até 25 MHz, portanto, é necessário 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. Defina os bits CPOL e CPHA para determinar o relacionamento entre a transferência de dados e o clock da interface serial (consulte o diagrama na página 240)


Como estamos lendo as fichas de dados aqui, e sem considerar os circuitos, vamos estudar melhor a descrição textual dos bits CPOL e CPHA na página 704 (Descrição Geral da SPI):



Fase do relógio e polaridade
Usando os bits CPOL e CPHA do registro SPI_CR1, quatro opções para relacionamentos de tempo podem ser selecionadas programaticamente. O bit CPOL (polaridade do relógio) controla o estado do relógio quando nenhum dado está sendo transmitido. Este bit controla os modos mestre e escravo. Se o CPOL for redefinido, o pino SCK estará baixo no modo ocioso. Se o bit CPOL estiver definido, o pino SCK estará em um nível alto no modo ocioso.
Se o bit CPHA (fase do relógio) estiver definido, a segunda extremidade do sinal SCK atua como um trap-gate do bit alto (descendente se o CPOL for limpo ou ascendente se o CPOL estiver definido). Os dados são capturados pela segunda alteração no sinal do relógio. Se o bit CPHA for apagado, a borda principal do sinal SCK atua como um trap-gate do bit alto (para baixo se o CPOL estiver definido ou para cima se o CPOL for apagado). Os dados são capturados pela primeira alteração no sinal do relógio.


Tendo fumado esse conhecimento, concluímos que os dois bits devem permanecer zeros, porque precisamos que o sinal SCK permaneça baixo quando não estiver em uso e que os dados sejam transmitidos ao longo da borda principal do pulso (consulte Rising Edge na folha de dados do DM634).


A propósito, aqui encontramos pela primeira vez um recurso de vocabulário nas folhas de dados do ST: nelas a frase “redefinir bit para zero” é escrita para redefinir um pouco e não para limpar um pouco , como, por exemplo, no Atmega.


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


Peguei especificamente o DM634 de 16 bits, para não me preocupar com a transmissão de dados PWM de 12 bits, como o DM633. DFF faz sentido colocar em uma unidade:


 #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 indica, avança o bit de ordem inferior. Mas o DM634 deseja receber dados começando com o bit alto. Portanto, deixamos isso de lado.


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


Instale o SSM e o SSI para esquecer 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 configurados (eles permanecem configurados apenas se um sinal alto for aplicado ao NSS)


Na verdade, com esses bits, atribuímos nosso SPI como mestre e o ativamos:


 #define MSTR 0x0004 #define SPE 0x0040 _SPI2_ (_SPI_CR1) |= MSTR; //SPI master //  ,  SPI _SPI2_ (_SPI_CR1) |= SPE; 

O SPI está configurado, vamos escrever funções que enviam bytes para o driver imediatamente. Continuamos a ler 25.3.3 "Configurando o SPI no modo mestre":



Procedimento de transferência de dados
A transmissão começa quando um byte é gravado no buffer Tx.
O byte de dados é carregado no registro de deslocamento em modo paralelo (a partir do barramento interno) durante a transmissão do primeiro bit, após o qual é transmitido em modo serial para o pino MOSI, o primeiro ou o último bit para frente, dependendo da configuração do bit LSBFIRST no registro CPI_CR1. O sinalizador TXE é definido depois que os dados são transferidos do buffer Tx para o registrador de deslocamento e uma interrupção também é criada se o bit TXEIE for definido no registrador CPI_CR1.


Destacamos algumas palavras na tradução para chamar a atenção para um recurso da implementação do SPI nos controladores STM. No Atmega, o sinalizador TXE ( Tx Vazio , Tx está vazio e pronto para receber dados) é definido somente após a saída do byte inteiro. E aqui esse sinalizador é definido depois que o byte é inserido no registro de deslocamento interno. Como é empurrado para lá por todos os bits simultaneamente (em paralelo) e, em seguida, os dados são transmitidos seqüencialmente, o TXE é definido antes do envio completo do byte. Isso é importante porque no caso de nosso driver de LED, precisamos puxar o pino LAT depois de enviar todos os dados, ou seja, Somente a bandeira TXE não será suficiente para nós.


E isso significa que precisamos de alguma outra bandeira. Vamos ver em 25.3.7 - "Sinalizadores de status":



<...>

Bandeira OCUPADA
O sinalizador BSY é definido e redefinido pelo hardware (gravar nele não afeta nada). O sinalizador BSY indica o estado da camada de comunicação SPI.
É redefinido:
quando a transferência estiver concluída (exceto no modo mestre, se a transferência for contínua)
quando o SPI está desativado
quando ocorre um erro no modo do assistente (MODF = 1)
Se a transmissão não for contínua, a bandeira BSY é apagada entre cada transmissão de dados.


Ok, venha a calhar. Descobrimos onde o buffer Tx está localizado. Para fazer isso, leia o "Registro de dados SPI":



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 o segundo para leitura (buffer de recebimento). A gravação no registro de dados grava no buffer Tx e a leitura no registro de dados retornará o valor contido no buffer Rx.


Bem, o registro de status, onde há sinalizadores TXE e BSY:



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 } 

Bem, como precisamos transmitir 16 bytes duas vezes, de acordo com o número de saídas do driver de LED, algo como isto:


 void sendLEDdata() { LAT_low(); uint8_t k = 16; do { k--; dm_shift16(leds[k]); } while (k); while (_SPI2_(_SPI_SR) & BSY); // finish transmission LAT_pulse(); } 

Mas ainda não sabemos como puxar o pino LAT, portanto, retornaremos à E / S.


Atribuir pinos


No STM32F1, os registros responsáveis ​​pelo estado dos pinos são bastante incomuns. É claro que existem mais do que o Atmega, mas eles também diferem de outros chips STM. Seção 9.1 Descrição geral do GPIO:



Cada uma das portas de entrada / saída de uso geral (GPIO) possui dois registros de configuração de 32 bits (GPIOx_CRL e GPIOx_CRH), dois registros de dados de 32 bits (GPIOx_IDR e GPIOx_ODR), um registro de configuração / redefinição de 32 bits (GPIOx_BSRR), 16 bits um registro de redefinição (GPIOx_BRR) e um registro de bloco de 32 bits (GPIOx_LCKR).


Incomuns, além de inconvenientes, os dois primeiros registradores estão aqui, porque 16 pinos de porta estão espalhados sobre eles no formato "quatro bits por irmão". I.e. os pinos de zero a sétimo estão na CRL e o restante na CRH. Ao mesmo tempo, o restante dos registradores se encaixam com êxito nos bits de todos os pinos da porta - muitas vezes permanecendo meio "reservados".


Para simplificar, comece no final da lista.


Não precisamos de um registro de blocos.


Os registros de configuração e redefinição são bastante engraçados, pois eles se duplicam parcialmente: você pode escrever tudo apenas em BSRR, onde os 16 bits mais altos redefinirão o pino para zero e os inferiores - configurados para 1, ou use também BRR, os 16 bits inferiores dos quais apenas redefinem o pino . Eu gosto da segunda opção. Esses registros são importantes, pois fornecem acesso atômico aos pinos:




Instalação atômica ou redefinição
Você não precisa desativar interrupções ao programar GPIOx_ODR no nível de bit: é possível alterar um ou mais bits com uma operação de gravação atômica APB2. Isso é possível escrevendo “1” no registro de configuração / redefinição (GPIOx_BSRR ou, somente para redefinição, em GPIOx_BRR), o bit que você deseja alterar. Outros bits permanecerão inalterados.


Os registradores de dados têm nomes bastante falantes - IDR = Input Direction Register, registro de entrada; ODR = Registro da direção de saída, registro de saída. No projeto atual, não precisaremos deles.


E, finalmente, os registros de controle. Como estamos interessados ​​nos pinos do segundo SPI, ou seja, PB13, PB14 e PB15, analisamos imediatamente o CRH:



E vemos que será necessário escrever algo em bits do dia 20 ao 31.


Já descobrimos o que queremos dos pinos, então aqui eu posso fazer sem uma captura de tela, basta dizer que MODE define a direção (entrada, se ambos os bits estão definidos como 0) e a velocidade do pino (precisamos de 50MHz, ou seja, ambos pino em “1”), e CNF define o modo: normal “push-push” - 00, “alternativa” - 10. Por padrão, como vemos acima, todos os pinos têm o terceiro bit da parte inferior (CNF0), ele os define como modo de entrada flutuante .


Como pretendo fazer outra coisa com esse chip, para simplificar, defini todos os valores possíveis de MODE e CNF para os registros de controle inferior e superior.


Bem, algo assim
 #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 localizados na porta B (o 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, você pode escrever definições para o LAT, o que irá contrair os registros 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, de alguma forma sempre foi, deixe-se ficar)


Agora tudo já está ótimo, simplesmente não está funcionando. Por ser o STM32, ele economiza eletricidade, o que significa que você precisa habilitar o relógio para os periféricos necessários.


Ativar o tempo


O relógio é responsável pelo relógio, eles também são Clock. E já podíamos ver a abreviação RCC. Estamos procurando na documentação: este é Reset e Clock Control.


Como foi dito acima, felizmente, as pessoas da STM fizeram a parte mais difícil do tópico de timing para nós, muito obrigado por elas (darei um link para o site de Di Halt para deixar claro o quão confuso isso é). Nós apenas precisamos dos registradores responsáveis ​​por habilitar os relógios periféricos (Peripheral Clock Enable Registers). Primeiro, encontre o endereço base do RCC, ele está no início do "Cartão de Memória":



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

E, em seguida, clique no link para tentar encontrar algo na placa ou, muito melhor, repasse as descrições dos registros de inclusão das seções sobre registros de ativação . Onde encontramos RCC_APB1ENR e RCC_APB2ENR:




E neles, respectivamente, bits, incluindo clock SPI2, IOPB (porta de E / S 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 uma oportunidade e desejar testar, conectamos o DM634 assim: DAI ao PB15, DCK ao PB13, LAT ao PB14. Alimentamos o motorista de 5 volts, não se esqueça de combinar a terra.



STM8 PWM


PWM no STM8


Ao planejar este artigo, por exemplo, decidi tentar aprender algumas funcionalidades de um chip desconhecido com apenas uma folha de dados, para não adquirir um sapateiro sem botas. O STM8 era ideal para esse papel: primeiro, eu tinha duas placas-mãe chinesas com o STM8S103; segundo, não é muito popular; portanto, a tentação de ler e encontrar uma solução na Internet fica na ausência dessas mesmas soluções.


O chip também possui uma folha de dados e manual de referência RM0016 , no primeiro endereço de pinagem e registro, no segundo - tudo o resto. O STM8 é programado em C no feio IDE ST Visual Develop .


Clock e E / S


Por padrão, o STM8 opera com uma frequência de 2 MHz, que deve ser corrigida imediatamente.



Relógio HSI (velocidade interna)
O relógio HSI é obtido de um oscilador RC interno de 16 MHz com um divisor programável (1 a 8). É definido no registro do divisor de relógio (CLK_CKDIVR).
Nota: no início, o oscilador HSI RC com divisor 8 é selecionado como a principal fonte de relógio.


Encontramos o endereço do registro na folha de dados, a descrição em refman e vemos que o registro precisa ser limpo:


 #define CLK_CKDIVR *(volatile uint8_t *)0x0050C6 CLK_CKDIVR &= ~(0x18); 

Como vamos iniciar o PWM e conectar os LEDs, olhamos para a pinagem:



O chip é pequeno, muitas funções estão suspensas nos mesmos pinos. O fato de que entre colchetes é "funcionalidade alternativa", é alternado por " opção de bytes " - algo como os fusíveis da Atmega. Você pode alterar seus valores programaticamente, mas não é necessário, porque nova funcionalidade é ativada somente após uma reinicialização. É mais fácil usar o ST Visual Programmer (downloads junto com o Visual Develop) que pode alterar esses bytes. A pinagem mostra que as conclusões de CH1 e CH2 do primeiro temporizador estão ocultas entre colchetes; é necessário colocar os bits AFR1 e AFR0 em STVP, e o segundo também transferirá a saída do CH1 do segundo temporizador do PD4 para o PC5.


Assim, 6 pinos controlam os LEDs: PC6, PC7 e PC3 para o primeiro temporizador, PC5, PD3 e PA3 para o segundo.


A configuração dos pinos de E / S no STM8 é mais simples e lógica do que no STM32:


  • familiarizado com o Atmega Data Direction Register : 1 = saída;
  • o primeiro registro de controle CR1 na saída define o modo push-pull (1) ou dreno aberto (0); desde que conecto os LEDs ao chip com cátodos, deixo zeros aqui;
  • o segundo registro de controle CR2 na saída define a velocidade do relógio: 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


Primeiro, vamos definir os termos:


  • Frequência PWM - frequência com a qual o timer está correndo;
  • Recarregamento automático, AR - valor de carregamento automático com o qual o temporizador contará (período de pulso);
  • Update Event, UEV - um evento que ocorre quando o cronômetro conta como AR;
  • Ciclo de trabalho PWM - ciclo de trabalho PWM, geralmente chamado de "ciclo de trabalho";
  • Valor de captura / comparação - valor para captura / comparação, tendo contado até que o timer fará alguma coisa (no caso de PWM, inverte o sinal de saída);
  • Valor de pré- carregamento - valor pré-carregado. O valor de comparação não pode mudar enquanto o cronômetro está correndo, caso contrário, o ciclo PWM será interrompido. Portanto, os novos valores transmitidos são colocados no buffer e retirados dali quando o temporizador chega ao final da contagem e é redefinido;
  • Modos alinhados à borda e alinhados ao centro - alinhamento na borda e no centro, o mesmo que o Atmelovskie Fast PWM e o PWM com correção de fase .
  • OCiREF, Sinal de referência de comparação de saída - o sinal de saída de referência, de fato, que no modo PWM está no pino correspondente.

Como já ficou claro na pinagem, dois temporizadores têm os recursos de PWM - o primeiro e o segundo. Ambos são de 16 bits, o primeiro possui muitos recursos adicionais (em particular, pode contar tanto para cima quanto para baixo). Precisamos que ambos trabalhem da mesma maneira, então decidi começar com o segundo obviamente mais pobre, para não usar acidentalmente algo que não está nele. Algum problema é que a descrição da funcionalidade PWM de todos os cronômetros no manual de referência está no capítulo do primeiro cronômetro (17.5.7 Modo PWM), portanto, é necessário pular para frente e para trás no documento.


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



PWM com alinhamento de arestas
Configuração da conta de baixo para cima
A contagem ascendente estará ativa se o bit DIR no registro TIM_CR1 for limpo
Exemplo
O exemplo usa o primeiro modo PWM. O sinal de referência OCiREF PWM é mantido alto enquanto TIM1_CNT <TIM1_CCRi. Caso contrário, é preciso um nível baixo. Se o valor para comparação no TIM1_CCRi for maior que o valor inicial (registre TIM1_ARR), o sinal OCiREF será mantido em 1. Se o valor para comparação for 0, o OCiREF será mantido em zero. ...


O temporizador do STM8 durante o evento de atualização verifica primeiro o valor de comparação e somente então fornece um sinal de referência. O cronômetro da Atmega primeiro embaralha e depois compara, como resultado, ao compare value == 0 a saída resulta em uma agulha que precisa ser combatida de alguma forma (por exemplo, invertendo programaticamente a lógica).


Então, o que queremos fazer: PWM de 8 bits ( AR == 255 ), consideramos de baixo para cima o alinhamento ao longo da borda. Como as lâmpadas estão conectadas ao chip por cátodos, o PWM deve emitir 0 (LED aceso) antes de comparar o valor e 1 depois.


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



110: Primeiro modo PWM - ao contar de baixo para cima, o primeiro canal fica ativo enquanto TIMx_CNT <TIMx_CCR1. Caso contrário, o primeiro canal está inativo. [mais adiante no documento copiar e colar incorreto do timer 1]
111: Segundo modo PWM - ao contar de baixo para cima, o primeiro canal fica inativo enquanto TIMx_CNT <TIMx_CCR1. Caso contrário, o primeiro canal está ativo.


Como os LEDs estão conectados aos cátodos MK, o segundo modo é adequado para nós (o primeiro também, mas ainda não sabemos).



Bit 3 OC1PE: Ativar pré-carregamento da saída 1
0: pré-carregamento do registro com TIMx_CCR1 desativado. Você pode escrever para TIMx_CCR1 a qualquer momento. O novo valor funciona imediatamente.
1: O registro de pré-carregamento em TIMx_CCR1 está ativado. As operações de leitura / gravação acessam o registro de pré-carregamento. O valor pré-carregado TIMx_CCR1 é carregado no registrador sombra durante cada evento de atualização.
* Nota: para que o modo PWM funcione corretamente, os registros de pré-carregamento devem estar ativados. ( TIMx_CR1 OPM).

, , , :


 #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 , :


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

-, , . , , 256. TIM2_PSCR :


 #define TIM2_PSCR *(volatile uint8_t *)0x00530E TIM2_PSCR = 8; 

. Capture/Compare Enable : , . , , .. PWM Mode 1. :


 #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; 

, , TIMx_CR1:



 #define TIM2_CR1 *(volatile uint8_t *)0x005300 TIM2_CR1 |= 1; 

AnalogWrite(), . Capture/Compare registers , : 8 TIM2_CCRxL TIM2_CCRxH. 8- , :


 #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; } 

, , 100% ( 255 ). , , .


, .


( , «» , ). . , .. , 16- Prescaler High Low . … . ?


1, , . 17.7.30 Break register (TIM1_BKR) , :




 #define TIM1_BKR *(volatile uint8_t *)0x00526D TIM1_BKR = (1<<7); 

, .



STM8 Multiplex


STM8


- , RGB- . – LED-, , - , , ( persistence of vision , ). - - .


:


  • RGB LED;
  • , ;
  • ;
  • RGB LED;
  • ...

.. , , «» . . , , , UEV RGB-.


LED , «», . :


 uint8_t colors[8][3]; 

, , .


 uint8_t cnt; 


, , CD74HC238. – , << . ( 0, 1 2) X, ( 1<<X ). . , – , , . , .


CD74HC238 , . P-MOSFET, , .. 20 , absolute maximum ratings . CD74HC238 :



H = , L = , X –


E2 E1 , E3, A0, A1 A3 PD5, PC3, PC4 PC5 STM8. , , push-pull .



, , :


-, Update Event (UEV), , LED. Update Interrupt Enable




 #define TIM2_IER *(volatile uint8_t *)0x005303 //enable interrupt TIM2_IER = 1; 

, ghosting – . - , , UEV, , LED - . (0 = , 255 = ) . I.e. , UEV .


:


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

r, g b 255 .



, - . - , .


ST Visual Develop, main.c stm8_interrupt_vector.c , . NonHandledInterrupt . .


, :



13 TIM2 /
14 TIM2 /


LED UEV, №13.


, -, stm8_interrupt_vector.c , №13 (IRQ13) :


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

-, main.h :


 #ifndef __MAIN_H #define __MAIN_H @far @interrupt void TIM2_Overflow (void); #endif 

, , main.c :


 @far @interrupt void TIM2_Overflow (void) { PD_ODR &= ~(1<<5); //   PC_ODR = (cnt<<3); //      PD_ODR |= (1<<5); //   TIM2_SR1 = 0; //   Update Interrupt Pending cnt++; cnt &= 7; //   LED TIM2_CCR1L = ~colors[cnt][0]; //      TIM2_CCR2L = ~colors[cnt][1]; //     TIM2_CCR3L = ~colors[cnt][2]; // return; } 

. rimProgramming Manual :


 //enable interrupts _asm("rim"); 

sim – . «», .


.



- , , . , .

Source: https://habr.com/ru/post/pt456094/


All Articles