对printf和scanf的实现
1.printf函数在格式化输出时,向下调用了char putchar(char c);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口putchar(),代码如下:char putchar(char c)
{
hal_uart_putchar(c);
return c;
}
其中hal_uart_putchar(c);函数是我们比较熟悉的了,是51单片机通过串口发送一个字节的函数,具体代码如下:
void hal_uart_putchar(char i)
{
}
有了这两个函数,在单片机启动后,首先进行串口初始化,接着就可以使用printf了……是不是很简单……
-------------------------------------------------------------------------------------------------------------------------------------
2.下面再看scanf的具体实现方法:
scanf函数在格式化输入时,向下调用了char getkey(void);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口getkey(),代码如下:
char _getkey (void)
{
return hal_uart_getchar();
}
其中hal_uart_getchar(); 稍稍复杂,但也很好理解,代码如下:
char hal_uart_getchar(void)
{
uchar ch;
//Wait until a character is available:
while(uart_rx_cnt == 0);
ES = 0;
ch = uart_rx;
uart_rx_rp = (uart_rx_rp + 1) % UART_BUF_SIZE;
uart_rx_cnt--;
ES = 1;
return ch;
}
这个函数是从串口接收队列中取出队尾的一个字节。uart_rx_cnt 表示现在串口队列中的已有字节数,uart_rx_rp 指向队尾。
最后要介绍的一个函数是串口接收中断函数,代码如下:
void UART1InterruptReceive(void) interrupt 4
{
ES=0;//关串行口中断
if(RI)
{
RI=0;//接收中断信号清零,表示将继续接收
if(uart_rx_cnt < UART_BUF_SIZE)
{
uart_rx = SBUF;
uart_rx_wp = (uart_rx_wp + 1) % UART_BUF_SIZE;
uart_rx_cnt++;
}
}
ES=1;//开串行口中断
}
该函数实现了串口的中断接收,收到的新的字节存放在队首,即uart_rx_wp指向队列的首地址,每次收到一个新的字节,uart_rx_cnt增1。
至此,scanf函数也可以实现了。
感谢分享 你的描述详细地介绍了如何实现printf和scanf函数,通过UART在51单片机上进行串口通信。这是一个很好的例子,如何使用自定义的putchar和getkey函数,配合UART中断实现输入输出。 在单片机中实现printf和scanf函数通常涉及到对标准输入输出库的替换或者自定义实现,因为单片机的资源有限,尤其是内存和存储空间,所以不能直接使用像PC上的标准库那样庞大的代码。 标准的scanf函数是从标准输入设备(通常是键盘)接收输入,但在单片机环境中,需要将scanf函数的输入重定向到串行通信接口。这通常通过定义一个新的_read函数来实现,该函数从串行通信接口读取字符。 在单片机中用scanf函数意义不大 很实用的知识。调试时,基本都用串口输出信息。 内存使用:由于单片机内存有限,需要优化内存使用,避免内存溢出。
缓冲区:printf和scanf通常使用缓冲区来提高效率。需要实现缓冲区管理,并在必要时刷新缓冲区。
硬件限制:需要考虑单片机的硬件限制,如波特率、I/O口数量等。 printf函数通常用于输出格式化的数据到串口。为了在单片机中使用printf,需要重定向标准输出流stdout到串口。这通常涉及到重定义fputc函数,因为printf底层是通过调用fputc来输出每个字符的。 这两个函数的实现通常依赖于硬件支持的串行通信接口 由于单片机的内存限制,需要对printf函数的代码进行优化,去除不必要的功能和冗余代码。 #include "stm32f1xx_hal.h"
// 输出函数,用于发送字符到USART
void USART_PutChar(char c)
{
HAL_StatusTypeDef status;
status = HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, HAL_MAX_DELAY);
if (status != HAL_OK)
{
// 错误处理
}
}
// 输出字符串到USART
void USART_Puts(const char *str)
{
while (*str)
{
USART_PutChar(*str++);
}
}
// 自定义printf函数
int printf(const char *format, ...)
{
va_list args;
char buffer; // 假设输出缓冲区大小
int len = 0;
va_start(args, format);
len = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
USART_Puts(buffer);
return len;
} // 输入函数,用于从USART接收字符
char USART_GetChar(void)
{
char c;
HAL_StatusTypeDef status;
status = HAL_UART_Receive(&huart1, (uint8_t*)&c, 1, HAL_MAX_DELAY);
if (status != HAL_OK)
{
// 错误处理
}
return c;
}
// 自定义scanf函数
int scanf(const char *format, ...)
{
va_list args;
char input; // 假设输入缓冲区大小
int len = 0;
int pos = 0;
va_start(args, format);
while (*format && pos < sizeof(input) - 1)
{
if (*format == '%')
{
format++; // 跳过'%'
switch (*format)
{
case 'd':
// 读取整数
while (isdigit((c = USART_GetChar())))
{
input = c;
}
sscanf(input, "%d", va_arg(args, int *));
break;
case 's':
// 读取字符串
while ((c = USART_GetChar()) != '\n' && c != EOF)
{
input = c;
}
input = '\0';
va_arg(args, char *);
break;
default:
// 忽略未知格式符
break;
}
}
else
{
// 普通字符
input = USART_GetChar();
}
format++;
}
input = '\0';
va_end(args);
return pos;
} #include <stdio.h>
#include <stdarg.h>
// 假设UART_HandleTypeDef huart1是你的串口句柄
extern UART_HandleTypeDef huart1;
// 重定义fputc函数
int fputc(int ch, FILE *f) {
// 将字符通过串口发送出去
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int main(void) {
// 初始化串口
// ...
// 使用printf输出信息
printf("Hello, World!\r\n");
while (1) {
// 主循环
}
} #include <stdarg.h>
// 发送一个字符到指定的输出设备(例如串口)
void uart_putchar(char c) {
// 在这里实现将字符发送到串口的代码
}
// 简化版的 printf 函数
void my_printf(const char *format,...) {
va_list args;
va_start(args, format);
while (*format) {
if (*format == '%') {
format++;
switch (*format) {
case 'd':// 整数
{
int num = va_arg(args, int);
char str;
int len = sprintf(str, "%d", num);
for (int i = 0; i < len; i++) {
uart_putchar(str);
}
break;
}
case 's':// 字符串
{
char *str = va_arg(args, char *);
while (*str) {
uart_putchar(*str++);
}
break;
}
// 可以根据需要添加更多的格式支持
}
} else {
uart_putchar(*format);
}
format++;
}
va_end(args);
}
int main() {
my_printf("Hello, %d!\n", 123);
return 0;
} 将printf函数的输出重定向到单片机的某个外设,比如UART(串口),这样可以通过串口将数据发送到PC或其他设备。 #include <stdarg.h>
#include "uart.h" // 假设有一个UART库用于发送数据
void my_putchar(char c) {
uart_send_byte(c); // 将字符发送到UART
}
void my_printf(const char *format, ...) {
va_list args;
va_start(args, format);
while (*format) {
if (*format == '%') {
format++;
switch (*format) {
case 's': {
char *str = va_arg(args, char*);
while (*str) {
my_putchar(*str++);
}
break;
}
case 'c': {
char c = (char)va_arg(args, int);
my_putchar(c);
break;
}
default:
my_putchar('%');
my_putchar(*format);
break;
}
} else {
my_putchar(*format);
}
format++;
}
va_end(args);
} 通常不直接提供标准C库中的printf和scanf函数。 printf和scanf函数的实现可能需要占用较多的内存和CPU资源,因此在资源受限的单片机上使用时需要谨慎。 在嵌入式系统中,特别是单片机环境中,并没有标准的控制台,因此需要对这些函数进行特殊的实现,以便通过串口或其他通信接口与外部设备进行交互。
页:
[1]
2