Como uso a função printf no STM32?

19

Estou tentando descobrir como usar a função printf para imprimir na porta serial.

Minha configuração atual é o código gerado pelo STM32CubeMX e o SystemWorkbench32 com a placa de descoberta STM32F407 .

Vejo em stdio.h que o protótipo printf é definido como:

int _EXFUN(printf, (const char *__restrict, ...)
               _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

O que isso significa? Onde está o local exato desta definição de função? Qual seria o ponto geral de descobrir como usar esse tipo de função para produzir?

user505160
fonte
5
Eu acho que você precisa escrever sua própria "int _write (arquivo int, char * ptr, int len)" para enviar sua saída padrão para sua porta serial, como aqui . Eu acredito que isso normalmente é feito em um arquivo chamado Syscalls.c que lida com "Remapeamento de chamadas do sistema". Tente pesquisar no Google "Syscalls.c".
Tut
1
Este não é um problema de eletrônica. Vá para o manual da sua biblioteca de compiladores.
Olin Lathrop
Você deseja fazer a depuração printf via semi-hospedagem (sobre o depurador) ou apenas printf em geral?
Daniel
Como o @Tut disse, a _write()função é o que eu fiz. Detalhes na minha resposta abaixo.
cp.engr
este foi um ótimo tutorial: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Respostas:

19

Eu obtive o primeiro método desta página trabalhando no meu STM32F072.

http://www.openstm32.org/forumthread1055

Como afirmado lá,

A maneira como consegui o printf (e todas as outras funções stdio orientadas ao console) para trabalhar foi criando implementações personalizadas das funções de E / S de baixo nível, como _read()e _write().

A biblioteca C do GCC faz chamadas para as seguintes funções para executar E / S de baixo nível:

int _read(int file, char *data, int len)
int _write(int file, char *data, int len)
int _close(int file)
int _lseek(int file, int ptr, int dir)
int _fstat(int file, struct stat *st)
int _isatty(int file)

Essas funções são implementadas na biblioteca C do GCC como rotinas stub com ligação "fraca". Se uma declaração de qualquer uma das funções acima aparecer em seu próprio código, sua rotina substituta substituirá a declaração na biblioteca e será usada no lugar da rotina padrão (não funcional).

Usei o STM32CubeMX para configurar o USART1 ( huart1) como uma porta serial. Como eu só queria printf(), só precisava preencher a _write()função, que fiz da seguinte maneira. Isso é convencionalmente contido em syscalls.c.

#include  <errno.h>
#include  <sys/unistd.h> // STDOUT_FILENO, STDERR_FILENO

int _write(int file, char *data, int len)
{
   if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
   {
      errno = EBADF;
      return -1;
   }

   // arbitrary timeout 1000
   HAL_StatusTypeDef status =
      HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);

   // return # of bytes written - as best we can tell
   return (status == HAL_OK ? len : 0);
}
cp.engr
fonte
E se eu usar um Makefile e não um IDE? Está faltando algo para funcionar no meu projeto Makefile ... Alguém poderia ajudar?
Tedi
@Tedi, você está usando o GCC? Esta é uma resposta específica do compilador. O que você tentou e quais foram os resultados?
cp.engr
Sim, eu uso o GCC. Eu encontrei o problema A função _write () foi chamada. O problema estava no meu código para enviar a string. Usei mal a biblioteca RTT de Segger, mas agora funciona bem. Obrigado. Todos trabalhando agora.
Tedi
4

_EXFUN é uma macro, provavelmente contendo algumas diretivas interessantes que informam ao compilador que ele deve verificar se a string de formato é compatível com printf e garantir que os argumentos para printf correspondam à string de formato.

Para aprender a usar o printf, sugiro a página de manual e um pouco mais de pesquisa. Escreva alguns programas C simples que usem printf e execute-os no seu computador antes de tentar fazer a transição para o micro.

A questão interessante será "para onde vai o texto impresso?". Em um sistema unix, ele vai para a "saída padrão", mas um microcontrolador não tem tal coisa. As bibliotecas de depuração do CMSIS podem enviar texto printf para a porta de depuração semi-hospedada do braço, ou seja, na sua sessão gdb ou openocd, mas não tenho idéia do que o SystemWorkbench32 fará.

Se você não estiver trabalhando em um depurador, pode fazer mais sentido usar o sprintf para formatar as sequências que você deseja imprimir e depois enviá-las por uma porta serial ou para qualquer monitor que possa estar conectado.

Cuidado: printf e seu código relacionado são muito grandes. Provavelmente isso não importa muito em um 32F407, mas é um problema real em dispositivos com pouco flash.

William Brodie-Tyrrell
fonte
IIRC _EXFUN marca uma função a ser exportada, ou seja, a ser usada no código do usuário e define a convenção de chamada. Na maioria das plataformas (incorporadas), a convenção de chamada padrão pode ser usada. Mas no x86 com bibliotecas dinâmicas, pode ser necessário definir a função __cdeclpara evitar erros. Pelo menos em newlib / cygwin, _EXFUN é usado apenas para definir __cdecl.
Erebos
4

A resposta de @ AdamHaun é tudo que você precisa, sprintf()é fácil criar uma string e enviá-la. Mas se você realmente quer uma printf()função própria, então Funções de Argumento Variável (va_list) é o caminho.

Com va_listuma função de impressão personalizada, é semelhante ao seguinte:

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void vprint(const char *fmt, va_list argp)
{
    char string[200];
    if(0 < vsprintf(string,fmt,argp)) // build string
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
    }
}

void my_printf(const char *fmt, ...) // custom printf() function
{
    va_list argp;
    va_start(argp, fmt);
    vprint(fmt, argp);
    va_end(argp);
}

Exemplo de uso:

uint16_t year = 2015;
uint8_t month = 12;
uint8_t day   = 18;
char* date = "date";

// "Today's date: 2015-12-18"
my_printf("Today's %s: %d-%d-%d\r\n", date, year, month, day);

Observe que, embora esta solução ofereça uma função conveniente para uso, mas é mais lenta que o envio de dados brutos ou o uso uniforme sprintf(). Com altos datarates, acho que não será suficiente.


Outra opção, e provavelmente a melhor opção, é usar o depurador SW-ST-Link junto com o utilitário ST-Link. E use Printf via visualizador SWO , aqui está o manual do ST-Link Utility , parte relevante começa na página 31.

O Printf via SWO Viewer exibe os dados de printf enviados do destino através do SWO. Permite exibir algumas informações úteis sobre o firmware em execução.

Bence Kaulics
fonte
você não usa va_arg, como você percorre os argumentos da variável?
iouzzr 28/02
1

printf () é (geralmente) parte da biblioteca padrão C. Se sua versão da biblioteca vier com código-fonte, você poderá encontrar uma implementação lá.

Provavelmente seria mais fácil usar sprintf () para gerar uma string e, em seguida, use outra função para enviar a string pela porta serial. Dessa forma, toda a formatação difícil é feita para você e você não precisa invadir a biblioteca padrão.

Adam Haun
fonte
2
printf () geralmente faz parte da biblioteca, sim, mas não pode fazer nada até que alguém configure o recurso subjacente para produzir o hardware desejado. Isso não é "invadir" a biblioteca, mas utilizá-la como pretendido.
Chris Stratton
Pode ser pré-configurado para enviar texto através da conexão do depurador, como Bence e William sugeriram em suas respostas. (Isso não é o que se pretendia, é claro.)
Adam Haun
Isso normalmente aconteceria apenas como resultado do código que você escolher vincular para dar suporte à sua placa, mas, independentemente de você alterar isso definindo sua própria função de saída. O problema da sua proposta é que ela não se baseia em entender como a biblioteca deve ser usada - em vez de sugerir um processo de várias etapas para cada saída, o que, entre outras coisas, prejudicará qualquer dispositivo portátil existente. código que faz as coisas da maneira normal.
Chris Stratton
O STM32 é um ambiente independente. O compilador não precisa fornecer stdio.h - é totalmente opcional. Mas se ele fornecer stdio.h, deverá fornecer toda a biblioteca. Os compiladores de sistemas independentes que implementam printf tendem a fazê-lo como comunicação UART.
Lundin
1

Para aqueles que lutam, adicione o seguinte a syscalls.c:

extern UART_HandleTypeDef huart1; // access huart1 instance
//extern int __io_putchar(int ch) __attribute__((weak)); // comment this out

__attribute__((weak)) int __io_putchar(int ch)
{
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return (status == HAL_OK ? ch : 0);
}

Conecte-se à sua porta COM via massa e você deve ser bom.

Michael Brown
fonte
1

Se você deseja uma abordagem diferente e não deseja sobrescrever funções ou declarar a sua, pode simplesmente usar snprintfpara arquivar o mesmo objetivo. Mas não é tão elegante.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
LeoDJ
fonte