C语言基础:
格式说明符:
printf
函数的格式说明符:
-
%d
:用于打印一个十进制的整数。 -
%i
:与%d
相同,用于打印一个十进制的整数。 -
%u
:用于打印一个十进制的无符号整数。 -
%x
:用于打印一个十六进制的整数(小写字母)。 -
%X
:用于打印一个十六进制的整数(大写字母)。 -
%o
:用于打印一个八进制的整数。 -
%f
:用于打印一个浮点数(小数)。 -
%e
或%E
:用于打印一个科学计数法表示的浮点数。 -
%g
或%G
:用于打印一个浮点数,根据数值的大小自动选择%f
或%e
格式。 -
%s
:用于打印一个字符串。 -
%c
:用于打印一个字符。 -
%p
:用于打印一个指针的值。 -
%%
:用于打印%
符号本身。
scanf
函数的格式说明符:
%d
:用于读取一个十进制的整数。%i
:与%d
相同,用于读取一个十进制的整数。%u
:用于读取一个十进制的无符号整数。%x
:用于读取一个十六进制的整数。%o
:用于读取一个八进制的整数。%f
:用于读取一个浮点数。%s
:用于读取一个字符串。%c
:用于读取一个字符。%p
:用于读取一个指针的值(通常不推荐使用)。
附加说明:
%n
:用于printf
和scanf
,%n
会将到目前为止输出/输入的字符数赋值给n
所指向的变量。%*[格式]
:*
表示忽略对应的输入/输出,不存储。%[范围]c
:[范围]
表示读取字符直到遇到指定的字符范围之外的字符。%[范围]s
:[范围]
表示读取字符串直到遇到指定的字符范围之外的字符。-
例如:
%05d
:打印一个至少5位的整数,不足部分用0填充。%6.2f
:打印一个浮点数,总宽度至少为6位,小数点后保留2位。%10s
:打印一个字符串,宽度至少为10位,如果字符串长度不足10位,则左侧用空格填充。
指针操作:
地址:类比于一个房间
数据:类比于一个房间的椅子
数组与指针:
在 C 语言中,指针和数组紧密相关。当有一个指向数组第一个元素的指针时,可以通过下标来访问数组的元素,就像使用数组名一样。
例如:
unsigned char mqtt_TxBuff[7][400];
unsigned char *mqtt_TxInPtr;
mqtt_TxInPtr=mqtt_TxBuff[0]时,那么可以使用mqtt_TxInPtr[0]来表示mqtt_TxBuff[0][0],mqtt_TxInPtr[1]来表示mqtt_TxBuff[0][1].
相当于mqtt_TxInPtr[0]表示的是mqtt_TxBuff[0][0]内元素的值,而不是地址。但只能把mqtt_TxInPtr[0]和mqtt_TxBuff[0][0]互换使用,不能当作完全相同的东西
指针和二维数组:
在一维数组中,例如b[12],b是一个数组名,可以代表数组的首地址,之后的地址b+1,b+2
在二维数组中,例如a[3][4],三行四列,每一行地址用a[0]、a[1]、a[2]表示,每行内元素的地址为
a[0]+1、a[0]+2...
枚举类型enum:
定义一个枚举类型,它会自动赋予(默认从0开始)每个常量分配了自定义的整数值,依次递增(也可以自定义需要自己全部写出来)。
Strlen和Sizeof的区别:
memcpy:
printf和sprintf的区别:
memset函数和strstr函数:
strcopy是将每个字符存放在数组中,str[0]=h,str[1]=a...
memset主要用于清零和初始化的作用
1、STM32基础
对寄存器的操作:
以配置GPIO为例
1、打开端口对应的时钟;
2、配置输出模式;
3、输出电平。
每一步都是:查找基地址+偏移地址,再进行配置。
例子:打开PC13,配置成低电平。
第一步:配置PC13的时钟
因为GPIOC_PC13属于APB2,选择端口C的时钟
首先找到地址:基地址为:0x40021000+偏移地址:0x18=实际地址:0x40021018
再对地址中具体的位进行操作:
打开端口C的时钟,首先将0x40021018转换为单片机能识别的地址
(unsigned int*)0x40021018
将地址里面的值取出来
*(unsigned int*)0x40021018
再改变里面的第四位(配置时钟完成)
*(unsigned int*)0x40021018|=(1<<4);
第二步:配置输出、输出模式
3、输出低电平
最终的代码:
对结构体的操作:
最终:
模块化:
编写.C文件
再编写.h文件
编写完成后一定要添加相应的抬头
库函数初始化函数的分析:
1、配置RCC
2、配置结构体
GPIO_InitStruct为结构体指针类型,需要对里面的成员进行赋值;结构体对引脚、引脚功能进行配置(都属于配置引脚),类型不同,所以需要在初始化时加上"&"的符号。
GPIOx为结构体类型,成员类型相同,只需要赋予结构体的地址,他的每个成员就有明确的地址,但它本身就是一个地址,所以不用取地址&的符号。
用标准库点亮一个LED灯:
配置的函数不能弄错(犯的第一个出错误)
模块化的步骤:
1、在USER文件夹添加.c和.h文件
不能自己在项目里添加,不然文件会保存到工程文件夹下。
2、3添加头文件路径
4、编写.C和.H文件
5、完成主函数
软件仿真和硬件仿真的设置:
首先点击Flie→Device database找到所使用的单片机类型
将DLL、Parameter复制到对应位置
点击ST_Link Debugger设置
调试过程:
打开对应的设置,查看相应程序的运行顺序
打开软件的示波器,可以观察对应波形的变化
STM32的中断:
中断用前四个位来表示优先级,两个为抢占优先级、两个为响应优先级
中断需要先考虑抢占优先级、再考虑响应优先级
SYSTICK定时器:
原理:
一般选用9M的频率(1s 9*10^6次)的时钟,首先赋值重装载寄存器,清空递减计数器并赋值。
所用到的寄存器:
STK_VAL控制寄存器:
STK_LOAD重装寄存器:
STK_VAL递减计数器:
编程思路(延时1s):
使用库函数延时(1s):
VAL寄存器以及LOAD寄存器都是24位的,它的最大值是1111 1111 1111 1111 1111 1111,转化乘十进制后是16777215。即装载的最大十周周期个数为16777215。
TIM定时器:
基本介绍:
PSC+1的原因:预分频器的工作方式是,每当定时器时钟源(比如内部时钟)“tick”一次,预分频器计数器的值就会增加1,直到达到预分频器设定的值(PSC值),然后计数器会再“tick”一次后归零,同时计数器(CNT)的值增加1。因此,为了得到实际的分频系数,需要在PSC的设定值上加1。
预分频器的作用:
1、降低时钟频率,以满足慢需求;
2、提高定时器的精度;
3、减小计数器的溢出
4、节能、提高兼容性、灵活性
定时器配置:
功能实现:
写定时器必须要写对应的中断
UART串口相关理论:
相关理论:
功能实现:
TXE标志位:
TC标志位:
因为状态寄存器(USART_SR)的复位值为0x00C0,TC在最开始就被置1了,所以在执行过程中,造成发送的第一个数据丢失。
实验代码和现象:
修改程序:清除TC位
发送一个字符串:
代码实现和实验现象:
接收一个字符串:
2、DH11模块
基本介绍:
传输过程:
总体过程:
DHT11的初始化,在MCU复位完成后,单片机与DH11进行输入输出切换。
发送数据:
结束:
代码部分:
MCU复位配置:
DH11初始化:
DH11为从机,初始化需要受到主机的监控,这样初始化主机才认为你的响应是成功的
单片机接受1位:
单片机接受一个字节:
单片机接受五个字节:
巧用printf函数:
执行printf函数需要几十us的时间,所以在时间精确到us的函数里面,不适合使用printf。
温湿度传感器中printf设置的delay时间一般间隔时间设置为1.1s。
3、IIC通信协议
相关理论:
注意:要读取(输入或者输出)SDA上的数据(SDA保持稳定),SCL必须是高电平。
代码部分:
起始信号:
数据传输:
OLED的第一个位包括地址、读写位等信息,由于IIC通信上的从机是与的关系,需要主机发送一个地址和读写信号,让对应的从机识别并操作。
代码和现象:
OLED引脚初始化:
产生IIC起始信号:
要先配置引脚的状态
产生IIC终止信号:
写入一个字节:
验证从机是否存在:
IIC上的每一个设备都会有一个固定的地址(查看参考手册),当主机在总线上发送一个地址,对应的从机识别后,会发送一个应答信号给主机(也就是这里的IIC_Write函数的功能中最后返回的应答位)
主函数:
实验现象:
4、OLED液晶屏
基本知识:
单片机通过控制SSD1306芯片控制OLED,通过IIC接口通信。
显示字符、汉字:
理论部分:
程序部分:
显示数字:
最终效果:
5、ESP8266WIFI
基本介绍:
6、MQTT理论
基本概念:
MQTT是一个云服务平台的协议,可以与不同的地方进行通信。
物联网平台下的名字解释:
具体操作:
1、创建阿里云平台
CONNECT报文-固定报头:
??表示最后CONNECT报文后面还有多少个字符,将字符数量化为16进制就是??的值。
CONNECT报文-可变报头:
CONNECT报文-有效载荷:
MQTT---CONNECT连接_mqtt--- connect连接-优快云博客
剩余长度:




服务质量:
订阅主题和订阅确认:
操作与CONNECT报文一致:






订阅确认:
返回0A是因为订阅主题的可变报头是0A
取消订阅与取消订阅确认:
发布消息与发布确认:
7、C语言实现CONNECT报文:
固定报头和可变报头部分:
固定的直接用数组存储即可,剩余长度为可变部分,用函数计算出长度即可。
当剩余长度超过128时,进行判断:
对remaining_len进行判断,低字节在前面,高字节在后面
例如:当remaining_len=256时。(如果超过128,低位中,要将第八位置1所以要与上0x80)
有效载荷部分:
客户端标识符:
【00+客户端标识符+客户端】
用户名:
【00+用户名标识符+用户名】
密码:
【00+28+密码】
代码部分:
while中的代码有错
应该是len=remaining_size%128;
remaining_size=remaining_size/128。
这里应该是
len=remaining_size%128;
remaining_size=remaining_size/128.
8、数据发送机制
通过WIFI端口,更改TCP和端口连接阿里云服务器。
模型部分:
容器的介绍:
发送的思路:
1、单片机发送数据给容器;
2、容器再发送数据给单片机;
判断是否有新命令的思路:
首先在一开始定义mqtt_TxInPtr=mqtt_TxOutPtr;
当单片机发送数据给容器时,执行命令mqtt_TxInPtr+=400,使得mqtt_TxInPtr会移到下一行,使得mqtt_TxInPtr≠mqtt_TxOutPtr(表明有新数据),再通过mqtt_TxOutPtr指针把数据发送出去;
发送完成后,mqtt_TxOutPtr也会移到下一行数据,从而又使得mqtt_TxInPtr=mqtt_TxOutPtr,为下一次数据做准备。
每行数据的前两位表示数据的长度,从第三位开始才是真正的数据
代码实现部分:
初始化容器与指针:
单片机发送数据到容器:
容器发送数据到阿里云:
主函数进行判断是否新数据和数据发送的功能是什么
总体代码:
9、数据接收机制
将发送WiFi模块的每个指令返回一个值用于判断是否连接成功,主函数的主题部分分为
if 连接上WiFi模块做什么
else 没连接上要做什么
模型部分:
数据接收过程:
代码部分:
1、首先建立一个接收容器
2、通过USAT2接收数据
首先在连接上阿里云服务器之前,主要用于WiFi接收数据;
USAT2的中断函数:当USAT2接收到数据时,会触发中断;将接收到的数据存放在USAT2_Buff[]容器中(缓冲区),并通过USAT2_RxCounter统计接收的字节数。
其次在连接上阿里云服务器之后;
用TIM4定时器判断是否一组数据,(函数if部分为未连接上阿里云服务器之前的USAT2执行的中断,else为连接上之后USAT2执行的中断)。
首次在接收到第一个数据后,开启TIM4定时器,然后继续接收数据,由于每接收一个字节都会对TIM4定时器进行清零,直到接收到下一组数据中,间隔时间超过40ms,触发TIM4定时器的中断。
数据被存在USART2_RxBuff[]中,数据的字节个数被存在USART2_RxCounter中。
TIM4定时器中断:
将USART2_RxBuff[]数据发送到容器中,同数据发送机制一样
3、主函数对接收容器中的数据进行判断
判断接收到的数据是否有对应的字节,输出相应的响应
10、订阅报文的发送
模型部分:
通过标志位来依次发送:
1、WiFi连接成功后,将Connect_flag置1;
2、Connect_flag为1时,发送Connect报文,成功后将ConnectPack_flag置1;
2、ConnectPack_flag为1时,发送SUBCRIBE报文,成功后将Subcribe_flag置1。
代码部分:
编写订阅报文部分:
主函数部分:
11、Ping报文
模型部分:
在订阅成功后,将Subcribe_flag置1,然后发送Ping报文,利用TIM3定时器三实现每隔30秒发送一次Ping报文。
代码部分:
Ping代码的优化部分:
使用一个标志位Ping_Flag去进行判断,产生中断后对Ping_Flag++,然后接收响应后在主函数置零。
如果没有响应,则间隔发送或者重新将WIFI标志位置零,重新连接。
12、Publish报文功能实现
模型描述:
程序部分:
主体函数思路:
Publish报文:
实验结束20241205