Làm cách nào để sử dụng chức năng printf trên STM32?


19

Tôi đang cố gắng tìm ra cách sử dụng chức năng printf để in ra cổng nối tiếp.

Thiết lập hiện tại của tôi là mã STM32CubeMX được tạo và SystemWorkbench32 với bảng khám phá STM32F407 .

Tôi thấy trong stdio.h rằng nguyên mẫu printf được định nghĩa là:

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

Nó có nghĩa là gì? Đâu là vị trí chính xác của định nghĩa hàm này? Điều gì sẽ là điểm chung của việc tìm hiểu làm thế nào để sử dụng loại chức năng này để đầu ra?


5
Tôi nghĩ bạn cần phải viết "int _write (int file, char * ptr, int len)" của riêng bạn để gửi đầu ra tiêu chuẩn của bạn đến cổng nối tiếp như ở đây . Tôi tin rằng điều này thường được thực hiện trong một tệp có tên Syscalls.c xử lý "Nhắc lại cuộc gọi hệ thống". Hãy thử Googling "Tòa nhà chọc trời.c".
Tut

1
Đây không phải là một vấn đề điện tử. Đi xem hướng dẫn cho thư viện trình biên dịch của bạn.
Olin Lathrop

Bạn có muốn thực hiện gỡ lỗi printf thông qua semihosting (qua trình gỡ lỗi) hay chỉ là printf nói chung?
Daniel

Như @Tut đã nói, _write()chức năng là những gì tôi đã làm. Chi tiết trong câu trả lời của tôi dưới đây.
cp.engr

đây là một hướng dẫn tuyệt vời: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Câu trả lời:


19

Tôi đã nhận được phương pháp đầu tiên từ trang này hoạt động trên STM32F072 của tôi.

http://www.openstm32.org/forumthread1055

Như đã nêu ở đó,

Cách tôi làm cho printf (và tất cả các chức năng stdio định hướng giao diện điều khiển khác) hoạt động là bằng cách tạo các triển khai tùy chỉnh của các chức năng I / O cấp thấp như _read()_write().

Thư viện GCC C thực hiện các cuộc gọi đến các chức năng sau để thực hiện I / O cấp thấp:

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)

Các chức năng này được triển khai trong thư viện GCC C như các thói quen còn sơ khai với liên kết "yếu". Nếu một khai báo của bất kỳ chức năng nào ở trên xuất hiện trong mã của riêng bạn, thói quen thay thế của bạn sẽ ghi đè khai báo trong thư viện và được sử dụng thay cho thói quen mặc định (không có chức năng).

Tôi đã sử dụng STM32CubeMX để thiết lập USART1 ( huart1) làm cổng nối tiếp. Vì tôi chỉ muốn printf(), tôi chỉ cần điền vào _write()hàm, mà tôi đã làm như sau. Điều này được quy ước chứa trong 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);
}

Nếu tôi sử dụng Makefile chứ không phải IDE thì sao? Một cái gì đó còn thiếu từ cái này để làm việc trong dự án Makefile của tôi ... Ai có thể giúp đỡ không?
Tedi

@Tedi, bạn đang sử dụng GCC? Đây là một câu trả lời dành riêng cho trình biên dịch. Bạn đã thử những gì, và kết quả là gì?
cp.engr

Có tôi sử dụng GCC. Tôi tìm thấy vấn đề. Hàm _write () đã được gọi. Vấn đề là trong mã của tôi để gửi chuỗi. Tôi đã sử dụng sai thư viện RTT của Segger nhưng bây giờ hoạt động tốt. Cảm ơn. Tất cả làm việc bây giờ.
Tedi

4

_EXFUN là một macro, có thể chứa một số chỉ thị thú vị nói với trình biên dịch rằng nó nên kiểm tra chuỗi định dạng để tương thích với printf và đảm bảo rằng các đối số để printf khớp với chuỗi định dạng.

Để tìm hiểu cách sử dụng printf, tôi đề nghị trang man và thêm một chút nữa. Viết một số chương trình C đơn giản sử dụng printf và chạy chúng trên máy tính của bạn trước khi bạn thử chuyển sang sử dụng nó trên micro.

Câu hỏi thú vị sẽ là "văn bản in đi đâu?". Trên một hệ thống giống như unix, nó chuyển sang "chuẩn ra", nhưng một vi điều khiển không có thứ đó. Các thư viện gỡ lỗi CMSIS có thể gửi văn bản printf đến cổng gỡ lỗi semihosting, tức là vào phiên gdb hoặc openocd của bạn nhưng tôi không biết SystemWorkbench32 sẽ làm gì.

Nếu bạn không làm việc trong trình gỡ lỗi, có thể có ý nghĩa hơn khi sử dụng sprintf để định dạng các chuỗi bạn muốn in và sau đó gửi các chuỗi đó qua một cổng nối tiếp hoặc tới bất kỳ màn hình nào bạn có thể đã đính kèm.

Coi chừng: printf và mã liên quan của nó rất lớn. Điều đó có lẽ không quan trọng lắm trên 32F407, nhưng đó là một vấn đề thực sự trên các thiết bị có ít đèn flash.


IIRC _EXFUN đánh dấu một chức năng sẽ được xuất, nghĩa là được sử dụng trong mã người dùng và xác định quy ước gọi. Trên hầu hết các nền tảng (được nhúng), có thể sử dụng quy ước gọi mặc định. Nhưng trên x86 với các thư viện động, bạn có thể cần xác định hàm là __cdeclđể tránh lỗi. Ít nhất là trên newlib / cygwin, _EXFUN chỉ được sử dụng để đặt __cdecl.
erebos

4

Câu trả lời của @ AdamHaun là tất cả những gì bạn cần, sprintf()thật dễ dàng để tạo một chuỗi và sau đó gửi nó. Nhưng nếu bạn thực sự muốn một printf()hàm của riêng mình, thì Hàm đối số biến (va_list) là cách.

Với va_listchức năng in tùy chỉnh trông như sau:

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

Ví dụ sử dụng:

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

Lưu ý rằng mặc dù giải pháp này cung cấp cho bạn chức năng thuận tiện để sử dụng, nhưng nó chậm hơn gửi dữ liệu thô hoặc sử dụng chẵn sprintf(). Với các cơ sở dữ liệu cao, tôi nghĩ rằng nó sẽ không đủ.


Một tùy chọn khác và có lẽ là tùy chọn tốt hơn là sử dụng trình gỡ lỗi ST-Link, SWD cùng với ST-Link Utility. Và sử dụng Printf thông qua trình xem SWO , đây là hướng dẫn của ST-Link Utility , phần có liên quan bắt đầu từ trang 31.

Printf qua SWO Viewer hiển thị dữ liệu printf được gửi từ mục tiêu thông qua SWO. Nó cho phép hiển thị một số thông tin hữu ích trên firmware đang chạy.


bạn không sử dụng va_arg, làm thế nào để bạn vượt qua các đối số biến?
iouzzr

1

printf () là (thường) một phần của thư viện chuẩn C. Nếu phiên bản thư viện của bạn đi kèm với mã nguồn, bạn có thể tìm thấy một triển khai ở đó.

Có lẽ sẽ dễ dàng hơn khi sử dụng sprintf () để tạo một chuỗi, sau đó sử dụng một hàm khác để gửi chuỗi qua cổng nối tiếp. Bằng cách đó, tất cả các định dạng khó được thực hiện cho bạn và bạn không phải hack thư viện chuẩn.


2
printf () thường là một phần của thư viện, vâng, nhưng nó không thể làm gì cho đến khi ai đó thiết lập khả năng cơ bản để xuất ra phần cứng mong muốn. Đó không phải là "hack" thư viện, mà là sử dụng nó như dự định.
Chris Stratton

Nó có thể được cấu hình sẵn để gửi văn bản thông qua kết nối trình gỡ lỗi, như Bence và William đề xuất trong câu trả lời của họ. (Tất nhiên đó không phải là điều người hỏi muốn.)
Adam Haun

Điều đó thường chỉ xảy ra do kết quả của mã bạn chọn để liên kết để hỗ trợ bảng của bạn, nhưng bất kể bạn thay đổi điều đó bằng cách xác định hàm đầu ra của riêng bạn. Vấn đề với đề xuất của bạn là nó không dựa trên sự hiểu biết cách thư viện được có nghĩa là để được sử dụng - chứ không phải làm điều đó bạn đang đề xuất một quy trình nhiều bước cho mỗi ouput, mà trong số những thứ khác sẽ làm cho một mớ hỗn độn của bất kỳ di động hiện có mã mà làm mọi thứ theo cách thông thường.
Chris Stratton

STM32 là một môi trường tự do. Trình biên dịch không cần cung cấp stdio.h - nó hoàn toàn là tùy chọn. Nhưng nếu nó cung cấp stdio.h, nó phải cung cấp tất cả thư viện. Các trình biên dịch hệ thống tự do thực hiện printf có xu hướng làm điều đó như giao tiếp UART.
Lundin

1

Đối với những người gặp khó khăn, hãy thêm những điều sau vào 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);
}

Kết nối với cổng COM của bạn thông qua putty và bạn sẽ ổn.


1

Nếu bạn muốn một cách tiếp cận khác và không muốn ghi đè các hàm hoặc khai báo hàm của riêng bạn, bạn chỉ cần sử dụng snprintfđể lưu trữ cùng một mục tiêu. Nhưng nó không thanh lịch bằng.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.