0%

============================
2022-8-18
============================

一.温故知新

    1.通过讲硬件的手册了解芯片内部结构
        CPU使用的是ARM-CORTEX-M3的核心,主频是72MHz
        CPU是通过三总线(数据 地址 控制)来访问外设的
        在STM32中AHB AHB1 AHB2都是三总线叫做高速总线
        在STM32中APB1 APB2也是三总线叫做外设总线
        芯片的管脚是多功能复用型的管脚
    2.GPIO的输出
        驱动LED灯
        选择了操作外设的寄存器的开发方式(需要使用C语言的位运算符)
        寄存器的真实地址 = 基地址 + 偏移地址
        *(volatile unsigned int *)(0x40011000 + 0x0C)
        举一反三
        驱动蜂鸣器
        BEEP	PC7
        管脚输出高电平,蜂鸣器响
        管脚输出低电平,蜂鸣器不响

二.GPIO的输入

    输入功能相当于检测,输出功能相当于控制
    输入功能分为
    上拉输入	管脚在没有外设操作的情况下,默认呈现的是高电平的状态
            对于低电平有触发效果
    下拉输入	管脚在没有外设操作的情况下,默认呈现的是低电平的状态
            对于高电平有触发效果
    浮空输入	管脚在没有外设操作的情况下,默认呈现的既不是高也不是低
            会对于外界的影响而变化
        (当在操作串口外设功能时会用到浮空输入)
    驱动开发板上的功能按键
    1.查看开发板原理图
        KEY0	PC9	上拉输入
        KEY1	PC8	上拉输入
        KEY2	PA0	下拉输入
        KEY0 KEY1在没有按下时应当检测到高电平,按下应当检测到低电平
        KEY2在没有按下时应当检测到低电平,按下应当检测到高电平
    2.查看芯片的手册
    #define GPIOC_IDR *(volatile unsigned int *)(GPIOC_BASE + 0x08)
    如何读取第9位的电平高低?
    ret = (GPIOC_IDR >> 9) & 0x01;
    3.写功能按键的驱动程序
    ST的官方固件大部分的参数都是按照位图来定义的
    以位图的形式定义
    GPIOA	0x0001	0001
    GPIOB	0x0002	0010
    GPIOC	0x0004	0100
    GPIOD	0x0008	1000
    GPIOA | GPIOB = 0011
    不是以位图的形式定义
    GPIOA	0x0001	0001
    GPIOB	0x0002	0010
    GPIOC	0x0003	0011
    GPIOD	0x0004	0100
    GPIOA | GPIOB = 0011

三.换一种开发方式

    需要调用ST公司的官方固件
    手册是辅助大家开发的,千万不要本末倒置

四.演示未来的开发方式

    STM32CUBEMX + Keil

===============================
作业
1.把今天讲过的内容完整的复习一遍
2.把<01test>项目工程拷贝一份,用LIB库实现LED灯、蜂鸣器、功能按键
3.完成需求
    通过3个功能按键,控制3个LED灯(开关)
    当按下抬起KEY0,LED0常亮,当再一次按下抬起KEY0,LED0常灭
    当按下抬起KEY1,LED1常亮,当再一次按下抬起KEY1,LED1常灭
    当按下抬起KEY2,LED2常亮,当再一次按下抬起KEY2,LED2常灭
===============================

=============================
2022-8-19
=============================

一.温故知新

    1.GPIO的输出实验->蜂鸣器
    2.GPIO的输入实验->功能按键
        检测按键是否被按下
        (轮询 和 中断)
    3.更换开发方式->调用ST公司的LIB库
    4.演示STM32CUBEMX工具的使用

二.位带(bitband)

    位带可以以原子操作的方式操作寄存器中的某些位
    1.引出位带
    ----------------------------------------------------
    例子 : 
    想要把十进制整数100,赋值到0x12345678地址中?
    *(volatile unsigned int *)(0x12345678) = 100;
    一次性操作32bit
    ----------------------------------------------------
    例子:
    想要把0x12345678地址中的第9bit,置1
    *(volatile unsigned int *)(0x12345678) |= (1 << 9);
    想要把0x12345678地址中的第8bit,清0
    *(volatile unsigned int *)(0x12345678) &= ~(1 << 8);
    一次操作1bit
    ----------------------------------------------------
    但是上述直接给某一个位赋值的操作,不是原子操作
    原子操作 : 就是不可分割的操作,原子操作在执行结束前,
        不会被任何信号或者任何中断打断
    
    2.介绍位带
    位带并不是C语言提供的,是硬件提供的,并不是STM32芯片提供的,
        而是由ARM-CORTEX-M3的核心提供的
    ----------------------------------------------------
    位带和位域有点类似
    ----------------------------------------------------
    打开<STM32RBT6\DataSheet\ARM-CORTEX-M>目录下
        <Cortex-M3权威指南CnR2(电子书).pdf>
    P83	查看存储器的映射
    ARM-CORTEX-M3支持访问4Gb的空间大小(物理的空间)
    只有片上SRAM开头的1Mb空间和片上外设的1MB空间支持位带访问
    位带的操作其实
    就是把位带区中的每一位都做一个地址别名,映射到位带别名区
    将来在访问位带别名区的空间时,就相当于是在访问位带区中某一位

    位带区的空间有1M个字节,等于有8M个位,每一位都做一个地址别名映射到
    位带别名区,映射的地址需要以4字节的方式对齐,所以
    位带区的1MB会在位带别名区膨胀成32MB

    虽然片上SRAM和片上外设都支持位带访问,
    但是片上外设的位带操作会用的更多一些(尤其是GPIO的输入与输出)

    3.位带转换的表达式
    可以把位带转换的操作封装成宏函数
#define BITBAND(ADDR, BITNUM)
((ADDR&0xF0000000)+0x2000000+((ADDR&0xFFFFF)<<5)+(BITNUM<<2))

#define MEM_ADDR(ADDR) *(volatile unsigned int *)(ADDR)

#define BIT_BAND(ADDR,BITNUM) MEM_ADDR(BITBAND(ADDR, BITNUM))
                    
#define GPIOA_IDR (GPIOA_BASE + 0x08)
#define GPIOA_ODR (GPIOA_BASE + 0x0C)

#define GPIOB_IDR (GPIOB_BASE + 0x08)
#define GPIOB_ODR (GPIOB_BASE + 0x0C)

#define GPIOC_IDR (GPIOC_BASE + 0x08)
#define GPIOC_ODR (GPIOC_BASE + 0x0C)

#define GPIOD_IDR (GPIOD_BASE + 0x08)
#define GPIOD_ODR (GPIOD_BASE + 0x0C)

#define PAOut(BITNUM) BIT_BAND(GPIOA_ODR, BITNUM)
#define PAIn(BITNUM) BIT_BAND(GPIOA_IDR, BITNUM)

PAOut(9) = 1;//把PA9管脚拉高
PAOut(8) = 0;//把PA8管脚拉低

    ret = PAIn(0);//读取PA0管脚的电平高低

    4.在项目工程中引入位带的操作
    

三.定时器

    流水灯的程序中需要使用延时(精确延时 和 粗略延时)
    精确延时需要使用定时器
    无论何种定时器都是由两个因素来决定定时长短的
    时钟频率 和 计数值
计数值	10    10      72000000  72000    72         72 / 1000
时钟频率	1Hz  10Hz  72MHz      72MHz  72MHz  72MHz
定时时间	10s   1s	   1s              1ms       1us       1ns
    1s = 1000ms = 1000000us = 1000000000ns

四.系统定时器

    通过最新选型手册可知,在STM32芯片中有非常丰富的定时器资源
    可以分为普通定时器和其他(特殊)定时器
    除了STM32芯片为程序员提供了定时器资源以外,ARM-CORTEX-M3的核心
    也为程序员提供了定时器的资源
    由ARM核心提供的定时器资源叫做系统定时器(system timer | systick)
    1.系统定时器的时钟频率
打开<STM32RBT6\DataSheet\STM32>目录下<STM32F10x中文参考手册.pdf>
P46	
    在STM32芯片中有多个时钟源
    HSI	系统内部的高速时钟	8MHz
    HSE	系统外部的高速时钟	8MHz
    PLL	锁相环(倍频器)	72MHz
    LSI	系统内部的低速时钟	40KHz
    LSE	系统外部的低速时钟	32.768KHz
    系统定时器的时钟频率 = SYSCLK / 8 或者 SYSCLK
    SYSCLK的时钟	可以由	HSI 或者HSE或者PLL提供
    已知HSI和HSE都是8MHz的时钟频率,都不足以达到72MHz
    所以只能是由PLL为SYSCLK提供时钟频率
    PLL是一个锁相环,是一个倍频器,本身是不会产生时钟频率的,
    需要有基础的时钟频率才能进行倍频
    PLL的基础时钟频率	可以由	HSI或者HSE来提供
    默认会选择HSE为PLL提供基础的时钟频率
    芯片外部的晶振产生的时钟频率要比芯片内部自己产生的时钟频率稳定的多

    2.系统定时器的计数值范围
    打开<STM32RBT6\DataSheet\ARM-CORTEX-M>目录下
    <Cortex-M3权威指南CnR2(电子书).pdf>
    P189	
    控制及状态寄存器
        第0位		开启系统定时器的计数
                0:关闭	1:开启
        第1位		是否触发中断
                0:不触发	1:触发
        第2位		时钟源的选择
                0:外部的	1:内部的
        第3-15位		保留
        第16位		用于判断计数器是否计数到0
        第17-31位		保留
    重装载值寄存器
        第0-23位		用来存放重装载值
        第24-31位		保留
    当前值寄存器
        第0-23位		用来存放当前计数值
        第24-31位		保留
    计数值的范围是[24个0 - 24个1](0 - 16M-1)

    3.调用官方固件驱动系统定时器
    官方提供的固件有问题
        1)只有开启系统定时器,没有关闭系统定时器
        2)默认开启了中断

==============================
作业
1.把这周讲过的内容完完整整的复习一遍
2.把这周写过的项目工程再重新写一遍
==============================

=============================
2022-8-22
=============================

一.温故知新

    驱动LED灯、蜂鸣器、功能按键、系统定时器
    直接操作外设寄存器、官方LIB库、官方HAL库
    是由ARM核心支持的位带的操作
    系统定时器(system timer | systick)
    是由ARM核心提供的定时器资源
    决定定时长短是由时钟频率和计数值
    时钟频率是通过时钟树的分析得出的SYSCLK / 8 或者 SYSCLK
    计数值是通过ARM的手册分析得出
    控制及状态寄存器		一共有32bit
        第0位		开启定时器的
        第1位		是否触发外部请求
        第2位		时钟源的选择
        第3-15位		保留
        第16位		判断计数是否计到0
        第17-31位		保留
    重装载值寄存器		一共有32bit
        第0-23位		存储重装载值[0 - 16M-1]
        第24-31位		保留
    当前值寄存器
        第0-23位		当前计数值[0 - 16M-1]
        第24-31位		保留

二.中断

    之前写了LED灯的流水灯程序->延时使用的是粗略延时(空循环)
        使用精确的延时->使用了系统定时器
    之前写了功能按键的程序->轮询的方法检测按键是否被按下
        while(1)
        {
            button_status(0);
        }
        由于轮询的方法浪费资源,可以替换成中断的方法
    目的 : 把轮询检测按键的程序,修改成中断的方式处理功能按键
    1.了解轮询和中断的关系
        轮询 : CPU被绑定到检测的函数上,一直不断的调用
        是非常消耗CPU资源
        中断 : 在中断到来之前CPU可以处理任何事情,当中断到来之后
        会打断CPU,CPU调用中断处理函数
    2.介绍中断控制器
        VIC : 向量中断控制器
        NVIC : 可嵌套的向量中断控制器
        GIC : 通用的中断控制器
        一般来说VIC和NVIC是在单片机中使用的中断控制器
        一般来说GIC是在高端芯片中使用的中断控制器
        VIC和NVIC 相对于 GIC来说,在硬件上支持中断向量
        NVIC 相对于 VIC来说,在硬件上支持中断嵌套
    3.我们芯片上的中断控制器
        在STM32F103RBT6芯片中使用的是NVIC
        前提 : 
        有些手册会把NVIC画到ARM核心的内部
        有些手册会把NVIC画到ARM核心的外部
        有些手册会把NVIC画到紧贴着ARM核心
        想要表达的含义就是NVIC和ARM核心是紧密相联的关系
    NVIC和ARM-CORTEX-M3之间是紧密相联的关系
    NVIC中断控制器会做两个操作
        1)会为每一个连接到NVIC上的中断源分配一个中断号
        2)处理优先级的问题
            组优先级(抢占优先级)
            子优先级(占先式优先级)

nvic

    4.GPIO触发中断的方法
        GPIO想要触发中断需要借助EXTI外设
        EXTI叫做外部中断
        EXTI仅仅是NVIC上的一个中断源而已
打开<STM32RBT6\DataSheet\STM32>目录下<STM32F10x中文参考手册.pdf>
    P89	NVIC以及异常向量表
    P91	EXTI的描述
        每组GPIO的0号管脚可以触发EXTI0
        每组GPIO的1号管脚可以触发EXTI1
        ...
        每组GPIO的15号管脚可以触发EXTI15
    ----------------------------------------------------
    已知KEY0按键使用的是PC9管脚,
    将来GPIOC9管脚想要触发中断需要使用EXTI9
    ----------------------------------------------------
    EXTI9只能由PC9触发吗?
    也可以是PA9 PB9 PC9触发,需要通过 AFIO外设 进行选择
    ----------------------------------------------------
    NVIC : 可嵌套的向量中断控制器
    EXTI : 外部中断
    EXTI仅仅是NVIC上的一个中断源而已
    ----------------------------------------------------
    中断号是中断号,外部中断号是外部中断号,是不一样的
    中断号 : 和异常向量表中异常处理函数的顺序有关
    外部中断号 : 和GPIO的管脚编号有关
    ----------------------------------------------------
    有些外部中断会自己享用一个中断号,有些外部中断会共用一个中断号
    

三.驱动外部中断

    把轮询检测按键的程序修改成中断的方式
    KEY0	PC9	EXTI9	上拉输入	下降沿触发
    KEY1	PC8	EXTI8	上拉输入	下降沿触发
    KEY2	PA0	EXTI0	下拉输入	上升沿触发
    需要通过AFIO外设为外部中断选择触发的管脚
    1.初始化eint
        1)通过APB2总线使能GPIOC组和GPIOA组以及AFIO的时钟
        (因为需要通过AFIO为外部中断选择触发的管脚)
        2)初始化功能按键使用的GPIO管脚
            1>把PC9 PC8管脚配置为上拉输入模式
            2>把PA0管脚配置为下拉输入模式
        3)为外部中断选择触发的管脚
            1>为EXTI9选择由PC9触发
            2>为EXTI8选择由PC8触发
            3>为EXTI0选择由PA0触发
        4)初始化NVIC
            1>配置组优先级和子优先级的所占比例
            2>选择中断号
            3>使能该中断
            4>配置组优先级的级别
            5>配置子优先级的级别

优先级所占比例

        5)初始化EXTI
            1>选择外部中断号
            2>使能该外部中断
            3>选择中断的模式
            4>选择触发的方式

============================
作业
1.把今天讲过的内容完完整整的复习一遍
2.把外部中断的项目工程重新搭建一遍
3.使用中断 + 消抖的方法实现按键开关的项目工程
============================

============================
2022-8-23
============================

一.温故知新

    1.目的
    之前写过检测按键是否被按下的程序->使用的是轮询的方式检测->CPU很忙
    是把轮询检测的程序修改成了中断的方式->需要使用中断控制器
    2.介绍芯片中常见的中断控制器
    VIC	向量中断控制器		单片机芯片中常用
    NVIC	可嵌套的向量中断控制器	单片机芯片中常用
    GIC	通用的中断控制器		高端芯片中常用
    3.在STM32F103RBT6芯片中使用的是NVIC中断控制器
    NVIC中断控制器会处理两个事情
        1)为每一个连接到NVIC上的中断源分配一个中断号
        2)处理优先级(组优先级 和 子优先级)
    异常向量表中存储的是异常处理函数的函数名(异常处理函数的入口地址)
    异常向量表中异常处理函数的顺序和中断号是一一对应
    4.GPIO需要借助EXTI来触发中断
    EXTI叫做外部中断
    每一组GPIO的0号管脚都可以触发EXTI0
    每一组GPIO的1号管脚都可以触发EXTI1
    ...
    每一组GPIO的15号管脚都可以触发EXTI15
    需要通过AFIO外设为外部中断选择触发的GPIO管脚
    

二.看门狗

    STM32F103RBT6芯片使用的核心是ARM-CORTEX-M3的核心
    在工控领域中经常会使用到看门狗外设,所以今天主要讲看门狗外设
    1.从最新选型手册中了解看门狗信息
打开<STM32RBT6\DataSheet\STM32>目录下<ST_MCU最新选型手册.pdf>
    P21
    看门狗属于定时器的资源,在STM32芯片中属于其他定时器资源
    在STM32芯片中有两个看门狗,分别是独立看门狗 和 窗口看门狗

    2.产生的问题
    1)查找看门狗的时钟频率?		40KHz
    2)查找看门狗的计数值范围?		[0 - 4095]
    3)看门狗提供的其他功能是什么?		重启系统

    3.看门狗的使用环境
    在工业的工作环境中,嵌入式设备经常会面临一些恶劣的工作环境:超高温、
    超低温、非常湿润、非常干燥、强烈的电磁干扰,这些环境都会影响嵌入式设备
    的正常工作,有可能会造成设备死机 或者 设备中运行的程序直接跑飞
    所以可以使用看门狗帮助程序员监测嵌入式设备是否死机或者程序跑飞
    一旦设备死机了或者程序跑飞了,看门狗可以帮助程序员重启系统

    4.看门狗的简介
    既然看门狗属于定时器的资源,那么决定定时长短是由时钟频率和计数值范围
    来决定的,
    和普通定时器不同,普通定时器定时到了之后,去处理程序员指定要做的事情
    看门狗定时到了之后,直接重启系统(效果和按下reset按键一样)
    如果设备死机了,看门狗可以帮助程序员重启系统
    如果设备没有死机,看门狗不需要重启系统
    (只要让看门狗的计数器永远计不到0,看门狗就不会重启系统了)

    5.喂狗(feed dog)
    需要每隔一段时间,往看门狗的计数器中重新写入计数值,让看门狗永远计不到0

    6.介绍STM32芯片内部的看门狗
    在STM32芯片中有两种看门狗
    独立看门狗 : (IWDG)
    窗口看门狗 : (WWDG)
    注意 : 一般来说高端的芯片中只有一个看门狗,更像是独立看门狗

    7.独立看门狗和窗口看门狗的区别
    相同点
        1)IWDG和WWDG都是由STM32芯片提供的外设资源
        2)IWDG和WWDG功能一样,都会因为设备死机或者程序跑飞,
            重启系统
        3)IWDG和WWDG都可以自行调节喂狗时间
    不同点
        1)时钟源不同
            IWDG使用的是独立RC(LSI<40KHz>)提供的时钟
            不需要使用总线使能时钟
            WWDG使用是APB1总线提供的时钟
            需要通过APB1总线使能WWDG的时钟
        2)产生中断不同
            IWDG是没有中断的,只要IWDG计到0,直接重启系统
            WWDG是有中断,只要计数器超时,可以触发中断
        3)计数的方式不同
            IWDG使用的是12bit的计数器
            WWDG使用的是7bit的计数器
        4)喂狗的时间范围不同
            IWDG只要计数器计到0之前,任意时间喂狗都可以
            WWDG会有一个上阙值和下阙值

三.独立看门狗

    打开<STM32RBT6\DataSheet\STM32>目录下
    <STM32F10x中文参考手册.pdf>
    P276
    IWDG使用的是LSI(40KHz)的时钟源
    IWDG是一个递减的计数器(向下计数)
    IWDG的计数器计数到0时会产生系统复位
    
    IWDG_KR(键寄存器)<写入特定的值会有特定的操作>
    0xAAAA		喂狗的操作
    0x5555		开启IWDG_PR和OWDG_RLR寄存器的写访问
    0xCCCC		开启看门狗
    
    IWDG_PR(预分频寄存器)<给IWDG的时钟频率进行分频的>
    注意 : 在操作IWDG_PR寄存器之前需要先给IWDG_KR寄存器写入0x5555
    开启IWDG_PR寄存器的写访问(关闭写保护)
    000 4分频		100 64分频
    001 8分频		101 128分频
    010 16分频	110 256分频
    011 32分频	111 256分频

    IWDG_RLR(重装载值寄存器)<是用来存储计数值的>
    注意 : 在操作IWDG_RLR寄存器之前需要先给IWDG_KR寄存器写入0x5555
    开启IWDG_RLR寄存器的写访问(关闭写保护)
    该寄存器只有低12bit有效
    可以存储的计数值是[12个0 - 12个1]<0 - 4095>

四.传感器

    1.传感器的简介
    在物联网嵌入式的项目框架中,需要使用嵌入式设备采集感知周围的数据
    那么需要使用传感器进行采集和感知,在市面上的传感器的种类有很多
    检测气体 : 温度、湿度、空气中的微颗粒物(PM2.5、PM1.0)
        稀有气体的含量(一氧化碳、二氧化碳、氧气、甲烷、乙醇、甲醛)
    检测液体 : 温度、浑浊度、PH值、液位
    检测固体 : 土壤湿度、微量元素的含量
    检测人体 : 体温、血压、血糖、心率
    ...
    本身市面上的传感器的种类有很多很多种
    2.传感器接口的简介
    GPIO接口、串口接口(USART/UART、IIC、SPI...)、并口接口
    数字型的传感器 : 可以直接采集到数字量,可以直接交给CPU去处理
    模拟型的传感器 : 采集到的是模拟量,需要经过AD转换,
        转换成了数字量再交给CPU去处理

五.DHT11传感器

    DHT11传感器是一个温湿度的传感器(是专门用来检测温度和湿度的)
    外观上来看是4引脚的封装形式
    打开<STM32RBT6\DataSheet\传感器>目录下
    <DHT11说明书.pdf>和<DHT22.pdf>
    1.DHT11是一个数字型的传感器
    2.DHT11是用来采集温湿度的数据
    3.DHT11是4引脚的封装形式
    4.测量范围		湿度:20%-90%	温度:0℃-50℃
    5.误差范围		湿度:±5%		温度:±2℃
    6.分辨力为1	DHT11只能检测到整数数据
    7.1pin连接电源 2pin连接MCU 3pinNC(悬空<没有连接>) 4pin连接地

上拉电阻

下拉电阻

    8.DHT11是串行接口 : 指的是数据以串行的方式进行传输
         串行的方式 : 指的是按顺序一位一位的传输数据
    9.单线双向指的是半双工工作模式(发送的同时不能接收,接收的同时不能发送)

串行传输

    A.一次完整的数据传输为40bit,高位先出.
    
    例子 : DHT11的数据管脚连接到了STM32的PC10管脚
    需要通过PC10管脚发送数据或者接收数据
    1)通过PC10管脚发送数据(高位先出)
    data = 10110100;
    data		&		PCOut(10)
第1次循环	10110100	10000000	1
第2次循环	01101000	10000000	0
第3次循环	11010000	10000000	1
第4次循环	10100000	10000000	1
第5次循环	01000000	10000000	0
第6次循环	10000000	10000000	1
第7次循环	00000000	10000000	0
第8次循环	00000000	10000000	0
        void func(u8 data)//通过PC10管脚把形参data存储的数据发送
        {
            u8 i = 0;//循环变量
            for(i =0 ; i < 8; i++)
            {
                if(data & 0x80)
                    PCOut(10) = 1;
                else
                    PCOut(10) = 0;
                data <<= 1;
            }	
        }

    2)通过PC10管脚发送数据(低位先出)
    data		&		PCOut(10)
第1次循环	10110100	00000001	0
第2次循环	01011010	00000001	0
第3次循环	00101101	00000001	1
第4次循环	00010110	00000001	0
第5次循环	00001011	00000001	1
第6次循环	00000101	00000001	1
第7次循环	00000010	00000001	0
第8次循环	00000001	00000001	1
        void func(u8 data)
        {
            u8 i = 0;//循环变量
            for(i = 0; i < 8; i++)
            {
                if(data & 0x01)
                    PCOut(10) = 1;
                else
                    PCOut(10) = 0;
                data >>= 1;
            }
        }
    3)通过PC10管脚读取数据(高位先出)
    DHT11要发送10110100
    PCIn(10)	data <<=1	data
第1次循环	1	00000000	00000001
第2次循环	0	00000010	00000010
第3次循环	1	00000100	00000101
第4次循环	1	00001010	00001011
第5次循环	0	00010110	00010110
第6次循环	1	00101100	00101101
第7次循环	0	01011010	01011010
第8次循环	0	10110100	10110100
        u8 func(void)
        {
            u8 data = 0;//保存读取到的数据
            u8 i = 0;//循环变量
            
            for(i = 0; i < 8; i++)
            {
                data <<= 1;
                if(PCIn(10) == 1)
                    data |= 1;
            }
            
            return data;
        }
    4))通过PC10管脚读取数据(低位先出)
    DHT11要发送10110100
    PCIn(10)	data >>=1	data
第1次循环	0	00000000	00000000
第2次循环	0	00000000	00000000
第3次循环	1	00000000	10000000
第4次循环	0	01000000	01000000
第5次循环	1	00100000	10100000
第6次循环	1	01010000	11010000
第7次循环	0	01101000	01101000
第8次循环	1	00110100	10110100
        u8 func(void)
        {
            u8 data = 0;//保存读取到的数据
            u8 i = 0;//循环变量
            
            for(i = 0; i < 8; i++)
            {
                data >>= 1;
                if(PCIn(10) == 1)
                    data |= 0x80;
            }
            
            return data;
        }

===========================
作业
1.把今天讲过的内容完完整整的复习一遍
2.可以使用操作寄存器的方法驱动一下独立看门狗
3.根据手册操作一下窗口看门狗
4.在纸上写一遍串行接口发送数据和接收数据(高位先出和低位先出)
===========================

=============================
2022-8-15
=============================

一.进入新的阶段

    STM32阶段(智能终端设备阶段)
    原由
    1.为了从应用层到底层内核驱动的过渡
    2.在高端的开发板上讲带操作系统的驱动,裸板驱动在STM32上讲
    3.国家大力的推广5G技术(海量物联)->所以需要讲智能终端设备

二.阶段性项目

    物联网智能终端的产品
    基础的物联网项目架构
    分为4个层次
        感知层
        网络层
        平台层
        应用层
    新型的物联网项目框架会加入边缘计算设备
    物联网的两个分支
    智能终端设备 : 单片机的内容
    边缘计算设备 : 高端芯片的内容
    小爱同学
    天猫精灵
    小度小度

基础的物联网项目架构

三.安装开发环境

    同学们在飞秋上,下载压缩包<STM32RBT6_SDK.zip>
    同学们在电脑上安装一个好用的PDF阅读器
    1.把windows操作系统上的文件扩展名先显示出来
    2.把压缩包<STM32RBT6_SDK.zip>解压一下
    3.工具包文件的认知
        <DataSheet>	存放的都是数据手册
        <ST官方固件>	存放的都是ST公司的官方固件
        <开发板原理图>	存放的是开发板原理图
        <开发工具>	存放的是单片机开发会用到的工具
    4.安装集成的开发环境
    在<STM32RBT6\开发工具\MDK520>中<MDK520.exe>
    双击安装
        1)在安装Keil时安装路径!!!千万不要有中文!!!
        2)在安装Keil时有填写 姓 名 公司名称 邮箱 都写数字 1 即可
        3)在安装最后有可能会弹出小对话框问是否安装驱动,选择安装
        4)安装结束会弹出网页以及联网安装补丁,都选择关闭

    手动安装补丁
    在<STM32RBT6\开发工具\MDK520>中5个.pack结尾的文件
    安装从上往下的顺序,依次双击安装
    
    仿真器的驱动
    STLink仿真器的驱动
    在<STM32RBT6\开发工具\STLink V2驱动\STLINK_V2_USBDriver>中
    如果Windows系统是64bit的就安装<dpinst_amd64.exe>
    如果Windows系统是32bit的就安装<dpinst_x86.exe>

============================
2022-8-16
============================

一.温故知新

    1.搭建了物联网项目框架
        感知层	智能终端设备的裸板驱动
            STM32F103RBT6
        网络层	WiFi模块
        平台层	华为	OceanConnect//太繁琐(安全性高)
            小米	小米IoT//企业用户
            阿里	AIoT//繁琐(安全性高)
            百度	天工//添加设备丰富
            移动	OneNet//简单
            联通	中国联通物联网//没用过
            东方瑞通	ESLink
        应用层	只要是提供了云平台都会提供应用层的APP
    2.在基础版本的物联网项目框架中引入边缘计算设备
    3.两个分支
        智能终端设备
        边缘计算设备
    4.安装开发环境

学习的时候注重的是过程
工作的时候注重的是结果

二.从表象到里象的方式了解开发板

    1.从外观入手(查看配件有哪些以及芯片的型号)
    打开<STM32RBT6\开发板原理图>目录下<stm32-mix.jpg>
    2.了解芯片的结构
    通过画图了解
    无论是SOC(高端的芯片)还是MCU(单片机芯片)
    都是可以自己组成微型的计算机系统	
    STM32F103RBT6 = CPU + 内存 + 硬盘 + 外设
        CPU = ARM-CORTEX-M3(用来做大量计算,处理数据)
        内存 = 运行程序时会占用的空间
        硬盘 = 保存烧写的程序
        外设 = 操作芯片功能的接口

01MCU内部结构

    S5P6818 = CPU + 内存 + 硬盘 + 外设
        CPU = ARM-CORTEX-A53(用来做大量计算,处理数据)
        内存 = 运行硬盘中固件会占用的空间
        硬盘 = 存储的是芯片厂商烧写好的固件(Read Only)
        外设 = 操作芯片功能的接口
    注意 : 高端芯片外部需要外接内存,外接硬盘

01SOC内部结构

三.介绍ARM公司和ST公司

    ARM公司是在英国剑桥成立,在2016年年底被日本软银收购
    嵌入式领域芯片架构
    曾经三足鼎立
        PPC	游戏机/掌机
        MIPS	路由器/交换机、网络探头
        ARM	手机
    现在
        MIPS	
        ARM	各种电子产品
    ST公司,意法半导体公司,在2020年国家统计数据,
    咱们国家使用ST公司的芯片NO.1

四.开发方式

    1.传统开发方式
        使用C语言的位运算符操作芯片外设的寄存器
        << >> & | ~ ^
        对于驱动开发工程师来说容易出错
        需要驱动开发工程师写驱动 + 业务逻辑
    2.流行开发方式
        使用ST公司的官方固件(LIB库)
        涉及到函数调用即可
        需要驱动开发工程师写驱动 + 业务逻辑
    3.未来开发方式
        使用STM32CUBEMX开发工具 + 官方固件(HAL库)
        涉及到函数调用即可
        需要驱动开发工程师写业务逻辑

五.搭建模板项目工程

    1.模板项目工程只有在刚开始会教大家搭建,后面的项目工程直接拷贝使用
    2.搭建模板项目工程的流程必须要会
    3.搭建模板项目工程的方法并不唯一

前提准备 : 把Windows系统下,文件的扩展名显示出来
    1.创建目录结构
    在<桌面>上新建文件夹,重命名为<stm32f103>
    在<桌面\stm32f103>目录中新建文件夹,重命名为<01test>
    在<桌面\stm32f103\01test>目录中新建文件夹
        cmsis	存放有关arm-cortex-m系列的文件
        fwlib	存放的是ST公司的官方固件
        mylib	存放的是程序员自己写的固件
        project	存放的是项目工程文件
        user	存放的是主文件
<STM32RBT6\ST官方固件\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport>目录下的所有文件
拷贝到
<桌面\stm32f103\01test\cmsis>

<STM32RBT6\ST官方固件\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm>
目录下的<startup_stm32f10x_md.s>
拷贝到
<桌面\stm32f103\01test\cmsis>

<STM32RBT6\ST官方固件\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver>目录下的<inc>和<src>
拷贝到
<桌面\stm32f103\01test\fwlib>

<STM32RBT6\ST官方固件\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template>目录下的
<main.c> <stm32f10x_conf.h> <stm32f10x_it.c>
<stm32f10x_it.h> <system_stm32f10x.c>
<system_stm32f10x.h>
拷贝到
<桌面\stm32f103\01test\user>	

    2.通过Keil搭建项目工程
    Define
    STM32F10X_MD,USE_STDPERIPH_DRIVER
        1>在桌面上运行KEIL软件
        2>如果默认打开的keil界面显示的是上一次的项目工程可以在keil的菜单栏<Project>中选择Close Project
        3>在keil的菜单栏<Project>中选择New μVision Project
        4>弹出了Create New Project对话框,选择新的项目工程创建的位置(选择之前项目工程创建的位置)
        5>弹出了Select Device For Target对话框,可以在Search这一栏录入要操作的芯片型号<STM32F103RB>
        6>点击左下角显示的找到的芯片型号,右边Description显示了芯片信息之后,点击OK
        7>会弹出Manager Run-Time Environment的对话框,直接关闭
        8>把keil默认的项目工程的层次结构调整成和windows下自己搭建的层次结构一致
        9>点击第三行菜单栏中的Manager Project Items
        A>在Project Targets这一栏中,把Target 1 修改成 Target //注意:双击蓝色的这一条可以编辑
        B>在Groups这一栏中,把Source Group1去掉,添加上cmsis fwlib mylib user 4个组
        C>首先在Groups这一栏中,选择cmsis这个组,然后在对话框右下角点击Add Files
        D>弹出对话框<Add Files To Group 'cmsis'>,默认的查找范围是project,找到自己创建的cmsis目录
        //注意:在这一步添加文件,只需要添加.c .s文件即可,不需要添加.h文件
        E>在cmsis目录中选择core_cm3.c文件和startup_stm32f10x_md.s添加到cmsis组中即可
        F>选择fwlib的组继续添加文件,找到stm32f10x_rcc.c文件,添加到fwlib组中
        G>选择user的组继续添加文件,找到main.c stm32f10x_it.c system_stm32f10x.c,添加到user组中
        H>在Manager Project Items的对话框中点击OK
        I>在keil界面的左边的Project栏中,找到main.c文件,双击打开
        J>在main.c文件中全选文件内容,全部删掉,加入自己写得main函数//注意:在文件最后一定要多加几个空行
        K>在keil的第三行菜单栏中选择 Options for Target 'Target'这一项
        L>在对话框中选择C/C++这一项,在Define:这一栏中填入 STM32F10X_MD,USE_STDPERIPH_DRIVER
        M>在C/C++这一项中,找到Include Paths,在这一项中添加头文件的路径
        N>在Include Paths这一行最后有一个三个点的按钮,点击,弹出Folder Setup对话框
        O>在Folder Setup对话框中选择4个路径即可 cmsis fwlib\inc mylib user
        P>添加完之后,点击OK,跳出对话框
        Q>开始编译项目工程,点击keil的第三行菜单栏的Rebuild的按钮
        R>在keil中设置仿真器,(1.安装仿真器的驱动 2.把开发板连接到电脑上 3.开发板上电)
        S>在keil的第三行菜单栏中选择 Options for Target 'Target'这一项
        T>在Options for Target 'Target'对话框的菜单栏中选择Debug,在Debug菜单中的右测下拉菜单中选择ST-Link Debugger
        U>选择ST-Link Debugger之后选择之后的Settings按钮,弹出Cortex-M  Target  Driver Setup对话框
        V>如果在SWDIO中确定有一串十六进制的数字,那么keil识别了开发板的仿真器
        W>在Cortex-M  Target  Driver Setup对话框中选择,Flash Download菜单,在Download Function中选择上Reset and Run
        X>在对话框中选择OK,退出对话框
        Y>在KEIL的第三行菜单栏中选择 Download 按钮,下载程序(确保程序编译没有错误没有警告)	

=================================
1.重新搭建项目工程
=================================

六.通过数据手册了解芯片内部的结构

    1.通过数据手册了解芯片内部外设的数量
打开<STM32RBT6\DataSheet\STM32>目录下<ST_MCU最新选型手册.pdf>
    硬盘		128Kb
    内存		20Kb
    封装形式		64管脚的封装形式
    定时器		4个16bit的定时器(普通定时器) + 其他定时器
            2个看门狗、实时时钟、24bit向下计数定时器
    ADC(模/数转换器)	16×12bit
    GPIO		51个
    串口		2个SPI、2个I²C、3个USART/UART、
            1个USB、1个CAN
    电压范围		2.0V - 3.6V
    <最新选型手册>在公司中是在项目立项的时候会使用(技术总监、组长)
    1.你选择的芯片的外设资源可以满足你的所有需求
    2.在满足第一点的条件下,其他外设资源越少越好

C语言执行流程

# 移出链表元素
给你一个无头单向不循环链表的首结点l和一个整数 val ,请你删除链表中所有等于 val 的节点,并返回新的首结点 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
struct llist_node* removeElements(struct llist_node* l, int val)
{
struct llist_node* cur = l;//指向要删除的结点
struct llist_node* back = NULL;//指向要删除的结点的前一个位置

while(cur != NULL)//判断是否遍历完所有结点
{
if(cur->val == val)//判断当前指向的结点是否是要删除的结点
{
if(back == NULL)//判断删除的是否是链表的首结点
{
l = cur->next;//让l指针指向下一个结点
free(cur);//释放首结点
cur = l;//让cur指针指向新的首结点
}
else//如果删除的不是首结点就按照普通的删除方法进行
{
back->next = cur->next;
free(cur);
cur = back->next;
}
}
else//如果不是要删除的结点,找下一个结点
{
back = cur;
cur = cur->next;
}
}
return l;//返回指向首结点的指针l
}
# 反转链表 给你一个无头单向不循环链表的首结点l ,请你反转链表,并返回反转后的链表首结点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
struct llist_node* reverseList(struct llist_node* l)
{
struct llist_node *cur = l;//cur指针指向当前操作的结点
struct llist_node *p = NULL;//p指针指向原链表中cur的下一个位置
struct llist_node *next = NULL;//next指针指向反转链表后cur的下一个位置

while(cur != NULL)//判断是否遍历完整个链表
{
p = cur->next;//p指针指向原链表中cur的下一个位置
cur->next = next;//让cur指针指向的结点的next指向前一个位置
next = cur;//移动next
cur = p;//移动cur
}
return next;
}
# 链表的中间结点 给定一个头结点为handler的单向不循环链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
//快慢指针
//慢指针一次走一步,慢指针一次走两步
struct llist_node* middleNode(struct llist_node* handler)
{
struct llist_node *s = handler;//慢指针
struct llist_node *f = handler;//快指针

while(f != NULL && f->next != NULL)
{
s = s->next;
f = f->next->next;
}
return s;
}
# 链表中倒数第k个结点 给定一个头结点为handler的单向不循环链表,输出该链表中倒数第k个结点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
//先让一个指针走k个结点,再让另外一个指针跟着走
struct llist_node* FindKthToTail(struct llist_node* handler, int k)
{
struct llist_node *s = handler;
struct llist_node *f = handler;

while(k--)
{
if(f == NULL)//判断k值是否大于链表节点数
return NULL;
f = f->next;
}
while(f != NULL)
{
s = s->next;
f = f->next;
}
return s;
}
# 合并两个有序链表 将两个升序的无头单向不循环链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
struct llist_node* mergeTwoLists(struct llist_node* l1, struct llist_node* l2)
{
struct llist_node *l = NULL;
struct llist_node *cur = NULL;

if(l1 == NULL)//判断l1链表是否不存在
return l2;
if(l2 == NULL)//判断l2链表是否不存在
return l1;
if(l1->val < l2->val)
{
l = cur = l1;
l1 = l1->next;
}
else
{
l = cur = l2;
l2 = l2->next;
}
while(l1 != NULL && l2 != NULL)
{
if(l1->val < l2->val)
{
cur->next = l1;
l1 = l1->next;
}
else
{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
if(l1 == NULL)
cur->next = l2;
else
cur->next = l1;
return l;
}
# 链表分割 现有一无头单向不循环链表的首结点指针l,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
struct llist_node* partition(struct llist_node* l, int x)
{
struct llist_node *cur = l;
struct llist_node *minhead = NULL;
struct llist_node *maxhead = NULL;
struct llist_node *mincur = NULL;
struct llist_node *maxcur = NULL;

minhead = malloc(sizeof(struct llist_node));
maxhead = malloc(sizeof(struct llist_node));
minhead->next = maxhead->next = NULL;
mincur = minhead;
maxcur = maxhead;

while(cur != NULL)
{
if(cur->val < x)
{
mincur->next = cur;
mincur = mincur->next;
}
else
{
maxcur->next = cur;
maxcur = maxcur->next;
}
cur = cur->next;
}
mincur->next = maxhead->next;
maxcur->next = NULL;
l = minhead->next;
free(minhead);
free(maxhead);
return l;
}
# 链表的回文结构 `现有一无头单向不循环链表的首结点指针l,请返回一个bool值,代表其是否为回文结构。` # 环形链表 `给你一个单向链表的头结点 head ,判断链表中是否有环。` # 单向不循环链表的排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
struct llist_node
{
int val;
struct llist_node *next;
};
*/
struct llist_node *pop_sort(struct llist_node *l)

struct llist_node *p, *q, *tail;
tail = NULL;
while((l->next->next) != tail)
{
p = l;
q = l->next;
while(q->next != tail)
{
if((q->val) > (q->next->val))
{
p->next = q->next;
q->next = q->next->next;
p->next->next = q;
q = p->next;
}
q = q->next;
p = p->next;
}
tail = q;
}
}

============================
2022-8-10
============================

note

一.温故知新
    栈式的存储结构
        先进后出
    队列式的存储结构
        先进先出
    无论是顺序表还是链表都可以实现
    栈式
    struct node
    {
        int stack[6];
        int ind;
    };
    队列
    struct node
    {
        int queue[6];
        int front;
        int rear;
    };

    静态库和动态库
早测 
1.链表中倒数第k个结点
给定一个头结点为handler的单向不循环链表
输出该链表中倒数第k个结点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct llist_node *func(struct llist_node *l, int k)
{
struct llist_node *s = l;//慢指针
struct llist_node *f = l;//快指针

while(k--)
{
if(f == NULL)//判断链表是否提前结束
return NULL;
f = f->next;
}
while(f != NULL)
{
s = s->next;
f = f->next;
}
}
2.合并两个有序链表 将两个升序的无头单向不循环链表合并为一个新的升序链表并返回 新链表是通过拼接给定的两个链表的所有节点组成的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct llist_node *func(struct llist_node *l1, struct llist_node *l2)
{
struct llist_node *l = NULL;//指向合并之后的链表
struct llist_node *cur = NULL;

if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
if(l1->val < l2->val)
{
l = cur = l1;
l1 = l1->next;
}
else
{
l = cur = l2;
l2 = l2->next;
}
while(l1 != NULL && l2 != NULL)
{
if(l1->val < l2->val)
{
cur->next = l1;
l1 = l1->next;
}
else
{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
if(l1 != NULL)
cur->next = l1;
if(l2 != NULL)
cur->next = l2;

return l;
}
3.简答题 给定一个有头单向不循环链表的头结点handler和要删除的结点p

内核链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/usr/src/linux-hwe-5.4-headers-5.4.0-122/include/linux
list.h

struct list_head
{
struct list_head *prev;
struct list_head *next;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD(handler);
替换成 struct list_head handler = {&handler, &handler}; ============================== 作业 1.把今天讲过的内容完完整整的复习一遍 2.把使用内核链表的功能补充完整 3.把早测的内容在电脑上写一遍 4.刷题 5.写项目 ==============================

test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>

struct list_head
{
struct list_head *prev;
struct list_head *next;
};

struct stu
{
int id;
char name[20];
int math;
struct list_head node;
};

int main(int argc, char *argv[])
{
struct stu s;

printf("&s = %p\n", &s);
printf("&s.id = %p\n", &s.id);
printf("s.name = %p\n", s.name);
printf("&s.math = %p\n", &s.math);
printf("&s.node = %p\n", &s.node);
printf("SIZE=%ld\n", (size_t)&(((struct stu *)0)->node));

return 0;
}
/*
struct stu a = 0;//假如说a的地址是0x12345678
struct stu *p = &a;//p 保存的地址是0x12345678

p->node
(size_t)&((struct stu *)(0)->node)
*/

kernellist

list.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#ifndef _MYTEST_LIST_H
#define _MYTEST_LIST_H

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

struct list_head {
struct list_head *prev;
struct list_head *next;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }
//struct list_head name = {&name, &name};
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}

static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}

static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}

#define list_entry(ptr, type, member) \
container_of(ptr, type, member)

#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)

/*
list_for_each(pos, head)
遍历链表
pos 指向每个数据节点的node
head 指向头节点的指针
list_entry(ptr, type, member)
得到数据的首地址
ptr node的地址
type 客户的结构体名
member 结构体中node的成员名
*/


#endif

kernellist.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
内核链表
有头双向循环链表
1.创建头结点
2.插入数据结点(头插法 尾插法)
3.遍历链表
4.销毁链表
==================
5.查找结点
6.删除结点
7.拿出结点
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

#define NAMESIZE 20

typedef struct stu
{
int id;
char name[NAMESIZE];
int math;
struct list_head node;
}STU;

void print(const void *data)
{
const STU *p = data;
printf("%d %s %d\n", p->id, p->name, p->math);
}

int id_cmp(const void *data, const void *key)
{
const STU *p = data;
const int *k = key;

return !(p->id - *k);
}

int name_cmp(const void *data, const void *key)
{
const STU *p = data;
const char *k = key;
return !(strcmp(p->name, k));
}

int main(void)
{
LIST_HEAD(handler);//创建头结点
STU *data = NULL;//将来指向开辟的空间,存储数据
STU *datap = NULL;//将来指向数据结点的首地址
struct list_head *pos = NULL;//pos指针指向每个数据结点
int i = 0;//循环变量
int find_id = 100;//要查找的ID号
char *find_name = "stu4";//要查找的名字

for(i = 0; i < 5; i++)
{
data = malloc(sizeof(STU));//开辟空间
if(data == NULL)
continue;
data->id = 100 + i;
sprintf(data->name, "stu%d", i);
data->math = 100 - i;
//list_add(&data->node, &handler);//头插法
list_add_tail(&data->node, &handler);//尾插法
}

//不要加 ; 因为list_for_each是一个宏
list_for_each(pos, &handler)//遍历链表
{
datap = list_entry(pos, STU, node);
//获取数据结点的首地址
print(datap);//打印数据
}

printf("----------------------\n");

list_for_each(pos, &handler)//查找结点
{
datap = list_entry(pos, STU, node);
if(name_cmp(datap, "stu9"))
{
printf("Find It!\n");
print(datap);
}
}

printf("----------------------\n");

list_for_each(pos, &handler)//销毁链表
{
datap = list_entry(pos, STU, node);//找到首地址
list_del(pos);//把数据结点从链表摘除
free(datap);//释放空间
pos = &handler;//指向头结点
}

return 0;
}