Ele olhou para mbed. À primeira vista, parecia muito interessante - uma estrutura independente de ferro, em C ++, com suporte para vários microcontroladores e placas de demonstração, um compilador on-line com integração ao sistema de controle de versão. Um monte de exemplos que convencem ainda mais a elegância da estrutura. Quase todas as interfaces do microcontrolador são acessíveis diretamente da caixa, usando as classes correspondentes já implementadas. Leve-o diretamente da caixa e programe em C ++ sem olhar para a folha de dados do microcontrolador - não é um sonho?A plataforma de teste é o STM Nucleo F030 de longa data, suportado por esta plataforma. Existem muitos bons tutoriais sobre como se registrar e iniciar o primeiro projeto, não falaremos sobre isso. Vamos direto ao interessante.Esta placa não contém muitos periféricos "on board". LED e botão - tudo isso é riqueza. Bem, o primeiro projeto - o clássico "Hello world" do mundo dos microcontroladores - pisca com um LED. Aqui está o código:#include "mbed.h"
DigitalOut myled(LED1);
int main() {
while(1)
{
wait_ms(500);
myled = myled ^ 1;
}
}
Bom, afinal, não? Você realmente nem precisa olhar para a folha de dados do controlador!A propósito, o clássico "Hello world" também está disponível imediatamente:#include "mbed.h"
Serial pc(USBTX, USBRX);
int main() {
pc.printf("Hello world\n\r");
while(1)
{
}
}
Isso não é realmente ótimo? "Pronto para uso", temos um console no qual uma impressão padrão do sistema funciona? A porta, a propósito, também aparece imediatamente quando a placa é conectada ao computador, junto com um disco virtual, no qual você só precisa copiar o binário montado - e a placa é reinicializada.Mas voltando ao LED piscando. Compilando, baixando para o quadro - piscando! Mas de alguma forma rápido demais, obviamente mais de uma vez por segundo, mas pelo menos cinco vezes ... Opa ...Estou um pouco distraído com as "abstrações vazias" mencionadas no título. Eu li sobre esse termo com Joel Spolsky. Aqui está sua citação: "Se eu ensino programadores em C ++, seria ótimo se eu não precisasse falar sobre aritmética char * e ponteiro, mas poderia ir direto para as linhas da biblioteca de modelos padrão. Mas um dia eles escreverão “foo” + “bar” e surgirão problemas estranhos, mas ainda preciso explicar a eles o que é char *. Ou eles tentarão chamar uma função do Windows com um parâmetro do tipo LPTSTR e falharão até aprenderem char * e ponteiros e arquivos de cabeçalho Unicode e wchar_t e TCHAR - tudo isso brilha através de falhas nas abstrações. "Portanto, por mais paradoxal que seja, a lei das abstrações holey não permite que se tome e comece a programar um microcontrolador como este (mesmo que este seja o “mundo olá” mais simples de três linhas), sem olhar para a folha de dados e não ter uma idéia de que o microcontrolador tem o mesmo interior, bem como o que está por trás de todas essas classes em abstração. Ao contrário das promessas dos profissionais de marketing ...Então, depois de suspirar pesadamente, começamos a pesquisar. Se diante de meus olhos não fosse uma abstração, mas um ambiente de desenvolvimento completo para o controlador, seria claro onde cavar. Mas onde cavar no caso do código acima? Se mesmo o conteúdo do arquivo mbed.h não permite que a abstração seja vista?Felizmente, a biblioteca mbed em si é de código aberto, e você pode baixá-lo e ver o que há dentro. Felizmente, ocorreu novamente que, através da abstração para o programador, todos os registros do microcontrolador são bastante acessíveis, embora não sejam declarados explicitamente. Já está melhor. Por exemplo, você pode verificar se temos uma velocidade de clock executando isso (para o qual eu tive que olhar nas planilhas de dados e mexer bastante): RCC_OscInitTypeDef str;
RCC_ClkInitTypeDef clk;
pc.printf("SystemCoreClock = %d Hz\n\r", HAL_RCC_GetSysClockFreq());
HAL_RCC_GetOscConfig(&str);
pc.printf("->HSIState %d\n\r", str.HSIState);
pc.printf("->PLL.PLLState %d\n\r", str.PLL.PLLState);
pc.printf("->PLL.PLLSource %d\n\r", str.PLL.PLLSource);
pc.printf("->PLL.PLLMUL %d\n\r", str.PLL.PLLMUL);
pc.printf("->PLL.PREDIV %d\n\r", str.PLL.PREDIV);
pc.printf("\n\r");
HAL_RCC_GetClockConfig(&clk, &flat);
pc.printf("ClockType %d\n\r", clk.ClockType);
pc.printf("SYSCLKSource %d\n\r", clk.SYSCLKSource );
pc.printf("AHBCLKDivider %d\n\r", clk.AHBCLKDivider );
pc.printf("APB1CLKDivider %d\n\r", clk.APB1CLKDivider );
Tudo saiu bem com a frequência: 48 MHz, como pretendido.Bem, cavar mais. Tentamos conectar um timer em vez de wait_ms (): Timer timer;
timer.start();
while(1) {
myled ^= 1;
t1 = timer.read_ms();
t2 = timer.read_ms();
while (t2 - t1 < 500)
{
t2 = timer.read_ms();
}
}
É lindo não? Mas o LED ainda pisca 5 vezes mais rápido do que o desejado ...Ok, nos aprofundamos ainda mais e vemos o que temos por trás do timer mágico, que, conforme a documentação promete, pode ser criado em qualquer quantidade, independentemente de quantos timers de hardware existem. microcontrolador. Legal, sim ...E no caso do STM F030, o temporizador de hardware do controlador TIM1 está escondido atrás dele, programado para contar tiques de microssegundos. E com base nisso, tudo o resto já foi construído.Então, já está quente. Lembre-se, eu disse que todos os registros estão disponíveis? Analisamos o seguinte:pc.printf("PSC: %d\n\r", TIM1->PSC);
Voila! Isso põe fim à investigação de quem é o culpado: o número 7 é enviado ao console. No registro PSC (escriba? É realmente apenas uma coincidência?) Há um valor, ao atingir o tempo em que o cronômetro irá gerar uma interrupção e começar a contar novamente. E nesta interrupção e o contador de microssegundos está desligado. E para que, na frequência de 48 MHz, a interrupção ocorra uma vez a cada microssegundo, não deve haver 7, mas 47. E 7 chegaram lá, provavelmente, no primeiro estágio de carregamento do microcontrolador, porque inicia em 8 MHz e, em seguida, retune o PLL para que a frequência seja multiplicada por 6, resultando nos 48 MHz desejados. E parece que o cronômetro está inicializando muito cedo ...Quem é o culpado, é claro. Mas o que fazer? As escavações da estrutura levaram às seguintes cadeias de chamadas:Primeiro: SetSysClock_PLL_HSI () -> HAL_RCC_OscConfig (), HAL_RCC_ClockConfig () -> HAL_InitTick () - ao alterar a frequência, chame a função que define o tick de microssegundo.Segundo: HAL_Init () -> HAL_InitTick () -> HAL_TIM_Base_Init () -> TIM_Base_SetConfig () -> TIMx-> PSC - chamado a partir de uma das funções mais globais, HAL_InitTick grava o valor necessário no registro PSC, dependendo do relógio atual freqüências ...O que permanece um mistério para mim é que é para a família STM F0 que a segunda cadeia não é chamada. Não encontrei chamadas para a função HAL_Init ()!Além disso, se você observar a implementação de HAL_InitTick (), logo no início, haverá as seguintes linhas: static uint32_t ticker_inited=0;
if(ticker_inited)return HAL_OK;
ticker_inited=1;
Chame esta função exatamente uma vez. Ou seja, você pode ligar para ela quantas vezes quiser, mas ela não fará nada para todas as chamadas subseqüentes. E o fato de a estrutura o chamar no caso de uma alteração na frequência do relógio é inútil ...Eu estava com preguiça de reconstruir a biblioteca, então a correção assumiu este formato: a primeira linha na função principal era esta:TIM1->PSC = (SystemCoreClock / 1000000) - 1;
Depois disso, o LED finalmente começou a piscar com a frequência correta de 1 Hz ...Eu me pergunto em que estágio alguém hipotético pararia, quem enganou os profissionais de marketing convencidos a tentar tornar a programação do microcontrolador tão fácil do zero? Se ele não encontrou microcontroladores antes?Ok, se não estou muito cansado, estamos seguindo em frente. Piscando um LED é bom, mas não é interessante. Eu quero algo mais O sensor de temperatura DS1820 foi encontrado em um maior e a biblioteca final foi pesquisada no Google. Fácil de usar, ocultando a complexidade de trabalhar com esse sensor interno. Tudo o que é necessário é escrever algo como#include "DS1820.h"
DS1820 probe[1] = {D4};
probe[0].convert_temperature(DS1820::all_devices);
float temperature = probe[0].temperature('c');
O que você acha que aconteceu após a compilação e o lançamento? Corretamente. Não funcionou :)Abstrações com vazamento, sim. Bem, parece que outro estudo empolgante dos internos da Mbed nos espera. E os detalhes do próprio sensor.O sensor é muito interessante. Com sua interface digital. Em uma linha, ou seja, funciona no modo half duplex. O controlador inicia a troca de dados, o sensor envia uma sequência de bits em resposta. Especialmente nesses casos, a estrutura mbed possui a classe DigitalInOut, com a qual você pode alterar a direção de seu trabalho no pino GPIO em tempo real. Algo assim:bool DS1820::onewire_bit_in(DigitalInOut *pin) {
bool answer;
pin->output();
pin->write(0);
wait_us(3);
pin->input();
wait_us(10);
answer = pin->read();
wait_us(45);
return answer;
}
O controlador envia um pulso “1 -> 0 -> 1” ao sensor, que é um sinal para que ele envie um bit em resposta. O que pode não funcionar aqui? Aqui está uma parte da folha de dados do sensor:
Como você pode ver, depois de enviarmos um impulso ao sensor, para ler o bit, temos até 15 microssegundos.Conectamos o osciloscópio e vemos que o sensor envia bits para si mesmo, conforme indicado na folha de dados. Mas aqui o controlador lê o lixo. Qual seria a razão?Não vou escrever muito sobre como vim para executar este código: pin->output();
t1 = timer.read_us();
pin->input();
t4 = timer.read_us();
pc.printf("Time: %d us\n\r", t4-t1);
Bem, quem adivinha, sem ler mais, quanto tempo em um microcontrolador com uma frequência de clock de 48 MHz é necessário para mudar a direção de um pino GPIO (de fato, é UMA gravação no registro, se houver), se for feito usando a estrutura mbed escrita em C ++ usando uma camada independente de ferro?13 microssegundos.E para ler a resposta do sensor, temos até 15. E mesmo se removermos completamente o wait_us (10) do código acima, o comando answer = pin-> read (); também leva um tempo maior que 2 microssegundos. O que é suficiente para não ter tempo para ler a resposta.Felizmente, foi possível enviar um impulso ao STM sem alterar a direção do GPIO. No modo de entrada, quando você conecta o resistor PullDown embutido, o efeito é o mesmo. Felizmente, chamar o modo pin-> (PullUp) em vez de pin-> input () requer apenas 6 microssegundos.O que foi suficiente para ter tempo de ler a resposta.E, finalmente, outro buraco na abstração. Depois que o DS1820 funcionou, o próximo sensor que surgiu foi o DHT21, um sensor que mede temperatura e umidade. Também com uma interface de 1 fio, mas desta vez uma resposta de codificação com duração de pulso. Parece que a solução óbvia seria pendurar essa linha na interrupção da entrada GPIO, o que facilita a medição da duração do pulso. E há até uma classe na mbed para isso. E funciona até como documentado.Mas o problema é que o sensor também funciona em half-duplex. E para ele começar a transmitir dados, ele precisa enviar um impulso "1 -> 0 -> 1", como no exemplo acima.E aqui mbed não permite isso. Ou você declara a porta como DigitalInOut, mas não pode usar interrupções. Ou você declara a porta como InterruptIn, mas não pode enviar nada para ela. Infelizmente, o truque PullDown não funcionou aqui. o sensor possui um PullUp embutido e o microcontrolador PullDown embutido não é suficiente para puxar o pino para 0.Eu tive que fazer um ciclo de polling de linha muito menos bonito como resultado. Tudo, é claro, funcionou assim, mas não funcionou lindamente. O que não seria um problema com o acesso direto aos registros ...Aqui está. Uma tentativa de fazer uma abstração para que qualquer pessoa possa começar imediatamente a programar microcontroladores dignos. Muitas coisas são realmente muito simplificadas e até funcionam. Mas, como qualquer abstração de alto nível, essa abstração também está cheia de buracos. E em uma colisão com qualquer um dos muitos buracos tem que descer do céu para a terra.