第五讲 C语言基础
1.C语言 USB-CDC串口之printf函数的实现
USB-CDC串口之printf函数可以帮助我们打印出变量的类型等,快速开发。
1.1 打开USB库中的 PRINTF_HID
宏定义(去掉 //
)
该步骤可选
我的:
#define PRINTF_USB //printf输出直接重定向到USB口
打开此”标志宏“,用于该头文件的92行的如下代码段:
int printf_usb (const char *fmt, ...);
#if defined PRINTF_SEGLED
#define printf SEG7_ShowString
#elif defined PRINTF_USB
#define printf printf_usb
#endif
即打开如上注释后可以使用 printf
来替换 printf_usb
,调用函数 int printf_usb (const char *fmt, ...);
。
其实不做这个步骤,程序中直接调用 printf_usb
也是一样的。
问题:==新USB库文件与此不同==
第5讲发现我的注释语句与冲哥的不同,我的 stc32_stc8_usb.h
里相同位置是:
#define PRINTF_USB //printf输出直接重定向到USB口
我的USB库文件是从官网下的,感觉应该是比冲哥的新。
另一点变化是现在库文件和例程也不是分开下载了,而是一个下载链接,库文件zip里就有例程。
**上节发现新例程里 与 冲哥视频里的 **main.c
对比,还少了一句代码:
IE2 |= 0x80;
==请冲哥或是哪位大侠给解读下,是否现在打开 PRINTF_USB
的宏定义也是一样的呢?==
==还有例程里 IE2 |= 0x80;
这句是不能少的吧?是新例程的错误吗?==
感谢论坛大佬答复:
【新提醒】《8051U深度入门到32位51大型实战视频》,【免费 + 包邮 送】实验箱@Ai8051U,100万套 - 第21页 - TFT彩屏,触摸屏,DMA-i8080/M6800并口自动刷屏,DMA-SPI刷屏,外设直接到外设 国芯技术交流网站 - AI32位8051交流社区
来自:乘风***
官网的USB库文件经过更新, printf
输出直接重定向到USB口的定义名称改成:PRINTF_USB
USB_HID
与 USB_CDC
都可以通过这个定义重定向到USB口输出 printf 数据。
==IE2 |= 0x80;
是使能USB中断,usb_init();
函数里面有设置,所以例子没有重新配置。==
==32系列 IE2
相关的中断位操作使能后,需要重新设置 EUSB
,安全起见可以在初始化完成,启动总中断前设置一次 IE2 |= 0x80;
==
“标志宏”或“空宏”
在C语言中,#define
指令是预处理指令的一种,用于定义宏。当你看到 #define
后面只有一个参数,比如 #define PRINTF_USB
,这意味着定义了一个名为 PRINTF_USB
的宏,但它没有替换文本或值。这样的宏通常被称为“标志宏”或“空宏”。
定义这样的宏有几个可能的用途:
-
****条件编译:** **标志宏经常用于条件编译。通过定义或不定义这些宏,你可以控制代码的哪些部分会被编译器编译,哪些部分会被忽略。例如:
#ifdef PRINTF_USB
// 只有当PRINTF_USB被定义时,这部分代码才会被编译
void print_to_usb(...) {
// 实现向USB打印的功能
}
#endif
在这个例子中,如果 PRINTF_USB
被定义了(即使它没有值),那么 print_to_usb
函数就会被编译。如果没有定义 PRINTF_USB
,那么这部分代码就会被编译器忽略。
-
****编译时检查:** **有些开发者使用标志宏作为编译时检查的一种方式。他们可以在代码中检查某个宏是否被定义,以此来确定是否包含了某些特定的功能或代码块。
-
****文档或注释:** **虽然这不是其主要用途,但有时标志宏也被用作一种文档或注释的形式,来指示代码的某个特定部分或功能是可选的或依赖于某些编译时条件的。
-
****占位符:** **在某些情况下,开发者可能会先定义一个空宏作为占位符,然后在未来的某个时间点为其添加具体的替换文本或值。
总的来说,#define PRINTF_USB
这样的定义本身并不直接产生任何代码或值,但它可以在编译过程中被用来控制代码的编译行为或作为其他预处理指令的一部分。
1.2 理解PRINTF的函数原型的定义
#define printf printf_hid
# 语法
#define NEW OLD
int printf_hid (const char *fmt, ...);
参数fmt -- 是格式控制字符串,包含了两种类型的对象:****普通字符和转换说明 。
-
普通字符:在输出时,普通字符将原样不动地复制到标准输出。
printf("8051U深度入门到32位51大型实战视频\r\n");
-
占位符(转换说明):不直接输出,用于控制 printf 中参数的转换和打印。每个转换说明都由一个百分号字符(%)开始,以转换说明符结束,如 %s
,从而说明输出数据的类型、宽度、精度等。
printf("8051U深度入门到32位51大型实战视频,%s\r\n","加油");
转换说明简介:
- 类型:根据不同的 fmt 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 fmt 参数中指定的每个 % 标签。关于附加参数,既可以是变量,也可以是常量。
- 位置:
printf()
函数的普通字符和转换说明放在" "双引号内,附加参数放在双引号外,每个附加参数之间用逗号隔开。
- **数量:printf() 的附加参数与转换说明符是⼀⼀对应关系,如果有 n 个转换说明符, **
printf()
的参数就应该有 n + 1
个。如果参数个数少于对应的转换说明符,printf()
可能会输出内存中的任意值。
printf_usb的fmt格式
- 类似printf,但似乎不完全等价。注意:
\t
: 8个空格
- 冲哥PPT里的
n.m
:n整数占几行,m表示小数占几行,应该是个错误。
**printf fmt **n.m
与限定宽度占位符、限定小数位数占位符
printf("%6.2f\n", 0.8); // 输出为 " 0.80"
说明:%6.2f 表示输出字符串最小宽度为6,小数位数为2。整体长度不足 6 位时,右对齐显示。
关于 printf
参考:https://blog.csdn.net/weixin_46672094/article/details/121618950
接收缓冲区 【HEX模式】展示
参考ASCII码表。
可变数量的参数
int printf_usb (const char *fmt, ...);
关于上句中三个点怎么理解?
在C语言中,函数声明中的三个点(...
)表示该函数接受可变数量的参数,也被称为**“可变参数列表”或“变长参数表”**。这种语法允许函数在调用时传入比定义时更多的参数,而这些额外的参数在函数内部可以通过特定的宏和类型转换来访问。
对于你给出的函数声明:
int printf_hid(const char *fmt, ...);
这里的 ...
意味着 printf_hid
函数除了接受一个 const char *
类型的 fmt
参数(通常是一个格式字符串)之外,还可以接受任意数量和类型的额外参数。这些额外参数的数量和类型在编译时是不确定的,而是在函数调用时由调用者决定。
**在C语言中,**处理可变参数列表通常需要使用 <stdarg.h>
头文件中的宏。以下是一个简化的例子,展示了如何在函数内部访问这些可变参数:
#include <stdio.h>
#include <stdarg.h>
int printf_hid(const char *fmt, ...) {
va_list args;
int result;
// 初始化可变参数列表
va_start(args, fmt);
// 假设这里我们简单地调用vprintf来打印格式化的字符串
// vprintf是标准库函数,它接受一个va_list类型的参数来处理可变参数
result = vprintf(fmt, args);
// 清理可变参数列表
va_end(args);
return result;
}
int main() {
printf_hid("Hello, %s! You are %d years old.\n", "Alice", 30);
return 0;
}
在这个例子中,printf_hid
函数内部使用 va_list
类型的变量 args
来存储可变参数列表。va_start
宏用于初始化 args
,使其指向第一个可变参数。然后,我们可以使用 vprintf
函数(它是 printf
的变体,接受 va_list
作为参数)来打印格式化的字符串和可变参数。最后,va_end
宏用于清理 args
。
需要注意的是,使用可变参数列表时要特别小心,因为编译器无法进行类型检查,这可能导致运行时错误。因此,在使用可变参数函数时,务必确保传入的参数与格式字符串中的格式说明符匹配。
1.3 测试
- 打开ISP - 【打开程序文件】,选择hex,查看时间确认一下。
- 【输入用户程序运行时的IRC频率】选择24MHz,
- 与上节一样:ISP-“硬件选项”Tab页-【选择CPU指令模式】:32-Bit。
- 与上节一样:勾选左下角【当目标文件变化时自动装载并发送下载命令】
- 与上节一样:“收到用户命令后复位到ISP监控程序区” Tab页,波特率:9600,勾选
- 使用默认的内部自定义命令"@STCISP#“
- 下次使用HID接口进行ISP下载
- 每次下载前都先发送自定义命令
- 收到用户命令后
- 【下载/编程】
****IRC频率是单片机的内部时钟频率。
****详细解释:
- IRC(Inner RC Oscillator 内部RC振荡器)是单片机内部的一种时钟源。
- IRC频率决定了单片机的运行速度以及单片机可以单位时间执行的指令数量,是单片机实现功能的关键因素之一。
- 在“Inner RC Oscillator”中,“RC”是“Resistor-Capacitor”的缩写,即“电阻-电容”。这种振荡器是由内部的电阻(R)和电容(C)元件组成的电路,用于产生振荡信号。RC振荡器是一种简单的振荡器类型,其振荡频率由电阻和电容的值决定,通常用于提供时钟信号给单片机或其他集成电路。不过,需要注意的是,RC振荡器的频率稳定性和精度可能相对较低,因此在某些高精度应用中可能需要使用其他类型的振荡器,如晶体振荡器(Crystal Oscillator)或陶瓷振荡器(Ceramic Oscillator)。
开发板的外部晶振频率是32.768MHz
2.数的进制:2进制、10进制、16进制
3.数据的基本类型
- 想要使用64位变量,需要在程序文件里面添加申明:
#pragma float64
,即需要使用 double
类型时,需要添加这句。
- short int和int都是2Bytes,在C251里是一样的。
#define u8 unsigned char
4.C语言常用运算符
遗留问题