STM32学习笔记(STM32F103RCT6)

STM32单片机 主要使用的即 接口编程技术 

单片机: MCU 微控制器/微处理器  
    将运行程序所需要的必要组件 集成到一个芯片上 
    即 芯片包含  
        1.CPU核心  
        2.RAM(运行内存)                运行的体量(程序大小)
        3.ROM(程序存储器 flash)  
        4.时钟单元(晶振/RC震荡电路)   运行速度
        5.外设功能                       功能强大否
            GPIO   TIMER/PWM  UART  IIC  SPI   ADC  RTC  USB  DAC  485  RS232  .....
            中断系统 
            
    单片机应用,多对成本 比较敏感
    多用于 控制领域  嵌入到受控器件内部 实现 数字化功能 
        传感器  控制器 

    单片机优势: 
        1. 极高的集成度  让大部分应用 外部电路极少即可以实现 
        2. 成本优势      0.1 - 10 - 200 300+ 
        
单片机的型号: 
    8位:  STC51  stm8  CC2541  CC2530 ....
    32位:  STM32  ESP32   CH32 ...
    
单片机开发: 
    1. 开发流程   交叉开发 
        程序编写 编译              PC机上 
        程序下载 运行  调试       在单片机上
    
    2. 开发工具   windows  Linux 
        程序编辑  编译 工具   windows(工具软件):  keil   IAR  STM32ide .... 
        程序下载 运行  调试   需要硬件(硬件工具,调试器,下载器) 
                不同的芯片厂商   器调试器 下载器 有所不同 
                
STM32单片机为例:
    ST 公司  意法半导体  
    STM32 由意法半导体生成的 一款  32位 MCU  使用cortex-M3架构(ARM架构)
    性能相较其他同类型单片机而言 更强 
    外设丰富  
    使用者众多, 资料丰富齐全 
    
课程以 智能门锁项目  为基础  讲解单片机应用   
    基于STM32F103RCT6 芯片为 核心  
    搭载  CH340(USB转串口), RGBLED, beep蜂鸣器, OLED显示屏, 矩阵键盘, 
            esp8266wifi模块, 电磁锁驱动电路
    
    STM32配套开发工具(软件/硬件):
    硬件:
        1.USB转串口模块 (智能门锁板载)      串口下载/串口调试
        2.ST-LINK工具  下载器/调试器     下载以及在线调试程序 
           杜邦线: 用于连接排针的一些线 
    软件: 
        程序模板.s: 生成  1.复制厂商的  2.使用工具生成 
        STM32的 初始工程代码  使用工具生成  cubeMX (官方提供的一个工具)
        cubeMX: 图形化界面 生成初始工程代码 
        
        程序编辑工具:  编辑 编译程序 
            keil5  
            CubeIDE: 集成 生成初始工程代码 以及 编辑 编译程序  程序功能 
            
        调试工具:
            ST-LINK 软件工具 与 硬件配套的 
        测试工具: 
            串口调试助手 
            字模取模工具 
            网络调试工具 
            
单片机项目开发流程: 
    1. 需求分析   功能  实现的思路  需要硬件支持
        智能门禁系统
            锁开关  主控   密码开锁(键盘)  人机交互反馈 屏幕 声音 灯光 
            远程开锁  网络 无线wifi 
            开锁手段  刷卡(NFC)  刷脸(摄像头)  指纹(指纹模块)
    2. 芯片选型  根据功能 以及程序 体量 
        智能门锁项目  采用  STM32F103RCT6 芯片 
        STM: 厂商 意法半导体
        32 :  32位 微控制器 
        F1 :  系列 
        03 :  增强型 
        R: 64脚
        C: 256K字节的闪存存储器  程序 
        T: LQFP 封装
        
    3. 制作实验板 开发板  外包采购 
        硬件工程师  
        
    4. 写程序测试 功能 
        开发环境搭建 
    
    5. 批量生产销售 
    
开发环境搭建:  参考环境搭建手册
    1. 安装 cubeMX  图形化界面 芯片配置工具 
    2. 安装 keil5   程序编辑编译工具  
    3. 安装 串口驱动 STLINK驱动 
    
    安装注意事项: 
    1. 安装路径中 尽量不用出现中文 
    2. 安装时  使用管理员身份运行 
    3. 安装时  关闭杀毒 和电脑管家 
    
下载.hex文件到stm32中  测试开发板
    1.连接开发板 到PC机  找到对应串口号
    2.打开 FlyMCU.exe  按图所示配置 下载 
    
1. 硬件原理图 
    关于STM32管脚相关  
    串口下载程序: 
    boot0(专用)  boot1(0) 
    用于指定 STM32的 启动时 的工作模式(启动位置)
    复位时:
    boot0 = 0;  从flash启动mcu  (启动你下载到flash中的程序)
    boot0 = 1;  从内部rom中启动 芯片自带的串口下载程序到flash的 一段代码 BootLoader
    
2. 第一个程序?
    点亮led
    1) 测试环境 ok?
    2) 验证流程 ok?
    
STM32 开发流程:
    主要编程方式有:
        1. 寄存器编程    编程者 直接访问寄存器操作硬件  直接快速  开发速度慢 麻烦
        2. 库编程        由芯片厂商  提供了一套专门用于操作 这块芯片的功能 库函数
            标准库       厂商不维护了  
            HAL库         ST公司主推的一套编程库  与CubeMX 工具配套使用 
                
    1) 工程模板 
        标准库  使用标准库模板开始编程 
        HAL库   CubeMX 工具 生成 定制 
    
    cubeMX   选择芯片 STM32F103RCT6 
        256k FLASH   代码内存
        48 k SRAM    运行内存 
        72M  主频 
        
    cubeMX 配置:  生成静态配置 
        1. 时钟单元 
        2. GPIO配置  
        PC1 LED  OUTPUT  输出低电平 
        
    生成代码:
    
    编辑编译:
    工程结构: 
        main.c           主函数入口文件 
        stm32f1xx_it.c  中断入口文件 
    
点亮led: 
    
    思考: HAL库 实现一些逻辑操作  led闪
    
HAL库的使用:
    gpio 操作函数:
    HAL_GPIO_Init        管脚初始化  
    HAL_GPIO_WritePin   写管脚   用于输出 
    HAL_GPIO_ReadPin    读管脚   用于输入 
    HAL_GPIO_TogglePin  管脚取反 用于输出  
    
练习作业: 
    控制 RGBLED 亮灭    PC6,7,8
    输入按键  检查  
    按键控制 开锁      PB12
    按键的点击实现   LED亮灭   颜色切换:R - G - B - 灭
    
 

GPIO功能:    
─ 输入浮空     input  没有上下拉
─ 输入上拉     input  有上拉
─ 输入下拉       input  有下拉   
─ 开漏输出     output  开漏   1: 断开(浮空/高阻态)      0: 漏 (电)漏进去 gnd 
─ 推挽式输出   output  推挽   1: 推 电流向外流动 vcc    0: 挽 (电)拉进来 gnd 

    复用功能: 接到其他控制器上了 不再由gpio控制 
─ 推挽式复用功能
─ 开漏复用功能    
─ 模拟输入       ADC     
    
STM32的中断系统:
    中断:  某种事件触发(中断源) 产生异常打断CPU, 使得CPU暂时停止当前任务的执行 
            转而去处理 该事件,  处理完成后 返回打断处 继续执行  
            
cortex-A    A9         --  GIC       通用中断控制器        更多的中断源管理    性能  大体量的代码运行
cortex-M    STM32     --  NVIC   嵌套向量中断控制器    更快的中断处理      实时性   响应速度快 

双核异构芯片: STM157a

实验2:   按键中断测试:  按键点击控制LED 
    外部中断配置: 
    1. gpio配置  中断模式 + 触发方式    是否上下拉 
    2. nvic配置  是否使能   配置优先级  

中断控制器        
嵌套向量: 
    高优先级的中断 可以打断 低优先级中断的执行 
    中断可以嵌套  
中断优先级 以及 中断使能的 程序设置 
    
  HAL_NVIC_SetPriority(EXTI2_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI2_IRQn);

写带中断嵌套的代码    时 注意  中断优先级问题  
    
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_2); //清除外部中断2
    
练习: 按键中断控制LED 

思考: 矩阵键盘如何驱动??

实验3: 串口通信 
    串行全双工异步通信方式  称串口 
    同步串口  : 有额外的时钟线 进行时钟同步 
    单线半双工串口 :  一根线 即收又发   
    
    协议:  波特率 bps  字长  启停位  校验  流控 
    
stm32串口发送数据到 PC机 实验: 
    线路: 
    PC -- USB-uart(模块) --- PA9,10 uart1 ---- STM32 

    STM32串口配置:
    1.使用 外部晶振 提供时钟 提高时钟稳定性 
    2.配置串口   发送           使能串口   配置串口参数  波特率字长校验...
                 不定长接收     额外配置   DMA  使能中中断 
    
    dma: 直接内存搬移控制器(硬件) 
        实现 内存到内存数据搬移  内存到设备   设备到内存  

    串口数据收发: 
    接收:
    HAL_UART_Receive        阻塞模型  有超时退出机制
        HAL_StatusTypeDef HAL_UART_Receive  ( UART_HandleTypeDef *  huart,  
      uint8_t *  pData,  
      uint16_t  Size,  
      uint32_t  Timeout  
     ) 
        huart: 串口句柄指针 
        pData: 要发送或接收的 数据容器地址 
        Size:  容器大小 单位字节 
        Timeout: 超时时间  单位ms 
        
    HAL_UART_Receive_IT     非阻塞模型 中断实现 
    HAL_UART_Receive_DMA    非阻塞模型 DMA实现 
    发送:
    HAL_UART_Transmit
    HAL_UART_Transmit_IT
    HAL_UART_Transmit_DMA
    
    串口回调函数  用于非阻塞模型中 
    串口接收完成 回调函数  
    void HAL_UART_RxCpltCallback  ( UART_HandleTypeDef *  huart ) 
    串口发送完成 回调函数  
    void HAL_UART_TxCpltCallback  ( UART_HandleTypeDef *  huart ) 
    
串口printf移植:
    printf("a=%d",a);
        1.格式化字符串 
        2.输出  重定向到 串口  
        
练习: 串口发送  printf移植 

    思考/尝试:  串口接收   

定长接收:     
    HAL_UART_Receive_IT     非阻塞模型 中断实现 
    HAL_UART_Receive_DMA    非阻塞模型 DMA实现     
    
HAL_StatusTypeDef HAL_UART_Receive_DMA  ( UART_HandleTypeDef *  huart,  
  uint8_t *  pData,  
  uint16_t  Size  
 ) 
    pData: 接收数据容器 
    Size : 容器大小  
    
串口的 不定长数据接收: 
    非阻塞模式  不定长数据  
    
不定长实现方式: 
    1. 串口空闲中断实现   STM32支持该中断 
    2. 串口发送的数据中 包含结束符号 
    
STM32 dma串口不定长数据接收  编程方式: 
    额外配置:
    1. 添加DMA 配置  使能串口通过DMA搬移数据 
    2. 使能串口中断   优先级 1 
    
程序上:
    1. main函数中   准备接收串口数据 
        //1. 使能串口空闲中断 
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
        //2. 启动串口dma 准备接收一次数据 
        HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buf, sizeof(rx_buf));
    
    2. 数据处理 在空闲中断触发时 
    写一个 串口空闲中断处理函数 
void uart1_idle_func(void)
{
    int len = 0;
    //1. 判断 是否真的是空闲中断 
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
    {// 真的是空闲中断触发了
        //2. 清除空闲中断 
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        //处理接收到的数据 
        //3. 计算接收到数据长度 
        len = sizeof(rx_buf) - hdma_usart1_rx.Instance->CNDTR;
        //4. 停止DMA 
        HAL_UART_DMAStop(&huart1);
        //5. 处理数据 打印调试 
        printf("接收到%d字节数据:%s\n",len,rx_buf);
        
        //6. 准备接收下一次数据
        memset(rx_buf,0,len);
        HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buf, sizeof(rx_buf));
    }
}    

    3. 在中断入口  stm32_f1xx_it.c 文件中  
    extern  void uart1_idle_func(void);

    void USART1_IRQHandler(void)
    {
      /* USER CODE BEGIN USART1_IRQn 0 */
        //串口中断入口
        uart1_idle_func();
        
      /* USER CODE END USART1_IRQn 0 */
      HAL_UART_IRQHandler(&huart1);
      /* USER CODE BEGIN USART1_IRQn 1 */

      /* USER CODE END USART1_IRQn 1 */
    }    

练习: 串口接收数据 
作业: 实现 串口发送指令  LDEON  LEDOFF 控制LED 

面试题
问: 单片机flash  是 nor flash or nand flash ?
    flash:  特性 
        1. 写入数据前 需要先擦除 然后才能写入  
        2. 按块擦除 且 写入速度 第一读取速度 
读数据:
    nor  flash: 可以按字节 读取   可以随机读取 任意字节       可以直接运行程序 
    nand flash: 读取数据 必须按块读取  可以随机读取 一块数据  不能直接运行程序 

stm32的  定时器: 
    实验4:  定时器实验 
    
    定时1S  亮灭LED: 
    定时器 定时1S功能配置 :
    
    1. 启用一个定时器  配置 时钟源 为 internal clock 
    2. 配置  分频值  重载值  
    1S  1hz  =  总线时钟 / (分频值+1) / 重载值 ;
        time2 总线时钟 APB1 timer clock  72M 
         
     1hz  =  72M / (7199+1) / 10K ;    
    
    3. 触发中断   使能nvic 定时器2中断
    
程序上:
    定时器启动: 
    HAL_StatusTypeDef HAL_TIM_Base_Start_IT (TIM_HandleTypeDef * htim)
    停止:
    HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef * htim)

思考: 
    一个按键: 点击 与 长按的 实现 ??
    思路: 按键(中断) 按下时  启动定时器   弹起时  判断  是否点击 
    定时器时间到(中断) 还没松手 是长按 
    
实现 按键 点击 控制 led-R 亮灭  长按控制  led_g 亮灭     

计时功能:  定时器的 计数寄存器 进行计数    精度高,  时间短

实验5: PWM   脉冲宽度调制
    
    PWM:  调制方波的  周期频率  占空比  
    用于   功率控制   直流电机调速   LED 亮度调节  颜色调制 
           逆变器  
           频率控制   无源蜂鸣器 
    
STM32的  pwm  还可以做输入模式: 
        测量输入的 PWM波形的 周期频率占空比 
           
        用于通信
        编码器接口模式   测量转动的 角度 以及 方向 
           
    实现原理  依托与定时器 

编码器  监测 转动方向 和 转动长度的 一种传感器  
    
    stm32的pwm 有定时器控制  每个定时器 支持 4路 pwm输出 
        这4路pwm  占空比可以不同 但  周期频率相同 
    
实验5 pwm 控制LED的亮度?
    
    配置: 
    1. 使能输出管脚 为 pwm功能 
    2. 使能对应定时器  和 其 pwm通道 
    3. 配置pwm参数 
        假设 led  频率 1K    占空比 10%   精度1%  0-100
        
        1k = 总线时钟(72M) /(719+1)/100 
        占空比 =  比较值 / 重载值
        
        10% =  10 / 100
        
程序上: 

    启动pwm 
    HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef * htim, uint32_t Channel)
    HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef * htim, uint32_t Channel)
    
    程序中修改 pwm参数:
    __HAL_TIM_SET_PRESCALER     设置分频值 
    __HAL_TIM_SET_COMPARE        设置比较值 
    __HAL_TIM_GET_COMPARE        读取比较值 
    __HAL_TIM_SET_COUNTER        设置计数值 
    __HAL_TIM_GET_COUNTER        读取计数值 
    __HAL_TIM_SET_AUTORELOAD      设置重载值
    __HAL_TIM_GET_AUTORELOAD    读取重载值

    舵机:    PWM    单线串口
        可以控制旋转角度,且可以保持
    SG90舵机:  50hz 方波  占空比  高电平时间0.5ms-2.5ms 

ADC:  实验6:
    stm32ADC 
    
    12位ADC   精度 数字量程 0-4095 
    ADC输入范围  模拟量测范围   参考电压  0-3.3V       5V - 8.3V
            VREF-  ~  VREF+ 之间的电压 

    ADC 公式   输入测量电压 / 参考电压  ==  测量数字值 / 数字量程 
               输入测量电压  = 参考电压 /  数字量程 * 测量数字值

    ADC应用: 
        模拟量 转换 数字量的 场合
        各种 模拟量传感器   温敏电阻  ...
        音频采集  ...
        电压监控 
    
STM32adc的使用:  测量电源电压 
    配置:  1.使能对应管脚 为ADC功能 
           2. 配置 ADC时钟  不超过14M 
           3. 配置ADC 其他分组参数 当有多个通道时 需要 
           
    程序上: 
        1.启动ADC 开始转换  
        HAL_StatusTypeDef HAL_ADC_Start (ADC_HandleTypeDef *
hadc)

        //1.启动ADC
        HAL_ADC_Start(&hadc1);
        //2.等待转换结束
        HAL_ADC_PollForConversion(&hadc1, 1);
        //3.得到转换结果
        adc_data= HAL_ADC_GetValue(&hadc1);
        printf("adc_data=%d\n",adc_data);
        //4.转换为电压 V
        V0 = 3.3f/4096 * adc_data;
        printf("V0=%.2fV\n",V0);
        HAL_Delay(500);

练习ADC:

    
RTC: 
    实时时钟:  实验7:   串口打印日历时间  
    配置: 
    1. 使能 低速时钟 
    2. 使能 RTC  
    3. 配置 RTC 使用外部低速时钟源  32.768Khz 
    
    
程序上 : 
HAL_StatusTypeDef HAL_RTC_GetTime (RTC_HandleTypeDef
* hrtc, RTC_TimeTypeDef * sTime, uint32_t Format)    
    
HAL_StatusTypeDef HAL_RTC_GetDate (RTC_HandleTypeDef
* hrtc, RTC_DateTypeDef * sDate, uint32_t Format)    


    //读取 rtc时间 
    HAL_RTC_GetTime(&hrtc, &tm, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &dm, RTC_FORMAT_BIN);
    
    printf("%d年%d月%d日 %02d:%02d:%02d 星期%d\n", 
    dm.Year + 2000, dm.Month, dm.Date, 
    tm.Hours, tm.Minutes, tm.Seconds, 
    dm.WeekDay);

实验8: 矩阵键盘驱动 
    方式1:  逐键扫描   判断按键按下否
    方式2:  行列扫描   
        先所有行 设置为  input 上拉   列设置为  output 0   得到按键按下的 行 
        再所有列 设置为  input 上拉   行设置为  output 0   得到按键按下的 列  
        
    方式3:  中断 
        先所有行 设置为 上拉 下降沿触发   列设置为  output 0
        某行触发中断了  确定该行  有 按键按下  
        然后再 中断中  再检查 哪一列触发了  
    
    矩阵键盘的 缺陷 : 
        1. 只能从 按键的 下降沿开始判断  
        2. 同一时间只能按一个键  软件可以解决 
    
    算法逻辑: 
        1. 配置 管脚 PA0-3 为 上拉 下降沿中断触发模式  
                管脚 PA4-7 为 输出 低电平 
            等待中断 
            
        2. 中断触发了  从中断触发的管脚可得  列 
        3. 进行 行扫描 
            配置 管脚 PA0-3 为 输出 低电平
                 管脚 PA4-7 为 输入 上拉 模式  
                 得到 行  
        4. 由 行 列 坐标得到 键值         

矩阵键盘驱动移植: 
配置上:
    1. 配置 PA0-3 上拉外部中断 下降沿触发模式  
        配置PA4-7 开漏输出低电平 
    2. 使能 PA0-3 外部中断 优先级 5
    
程序上: 
    1.添加驱动文件  keybord_exti.c .h 到工程目录 
    2.在 main函数中  调用 Key_Bord_Init();
    3.在 4个外部中断入口 调用 Get_KeyNum();
    4. 若按键点击了 全局变量 key 即 按键的键值 

练习 输入密码开锁  

用户数据的存储: 
    外接EEPROM 或 外接flash: 
    内部flash读写:
    
STM32内置flash的使用:
    实验 9:  记录 芯片  启动次数  
    
    flash 写操作: 
    1. 先擦除 指定flash 页 
    2. 再写入 数据  

实验10:    
IIC控制器 和  OLED显示屏: 
    
OLED显示屏: 点阵显示器   彩色(RGB) SPI或并行 /单色(0/1) IIC(2线)/spi(3根) 
    
    模块与模组: 模块可以上电直接使用,只需要与之通信 即可实现功能 
    模组: 需要一些必要的 或 额外的驱动电路才可以功能  定制性强 

STM32与0.96寸IIC屏幕模块
    OLED IIC 从机  ---->  PB6,7 IIC1控制器   STM32  主机 

cubemx IIC 驱动显示器 配置:
    1. 启用对应管脚以及  IIC 控制器
    2. 配置 IIC 控制器参数  快速模式  400Khz  

OLED控制数据协议:  参考 SSD1306 芯片手册 
    OLED iic接口  从机地址  7bit  0 1 1 1 1 0 SA0  + 1bit 读写标志 
                                               |--> 管脚 用于选择从机地址 
                                  0 1 1 1 1 0 1    0  == 0x7a
                                  0 1 1 1 1 0 0    0  == 0x78   本机模块 

    总线协议:
        7bit(从机地址) + 1bit(读/写) + 8bit(控制指令) + 8bit(数据值) ....
        
    控制指令: 
        参考 参考 SSD1306 芯片手册 
        
STM32 hal库  IIC通信函数: 
HAL_StatusTypeDef HAL_I2C_Master_Transmit
(I2C_HandleTypeDef * hi2c, uint16_t DevAddress, uint8_t *
pData, uint16_t Size, uint32_t Timeout)    
    
    hi2c: IIC 句柄 
    DevAddress:  从机设备地址 + 读写标志 
    pData:  要发送的数据  容器 
    Size :  要发送的数据 大小 
    Timeout: 超时时间 
    
子模: 文字的 显示模型  相当于一个文字的图片     
    
U8G2: 点阵图像驱动库
    移植:  参考示例 u8g2_test 

实验11:     
esp8266wifi模块: 
嵌入式设备联网 方式: 
    NBiot  iot  通过网络运营商 提供的基站进行通信   手机流量
        无距离限制 
    2.4G 射频模块: 
        无通信协议模块: SI24R1
        wifi:   (网络层)TPC/ip   TCP  udp    
        蓝牙:   ble 低功耗蓝牙  
        ZigBee: 局域网 自组网协议 

    有线: 485  CAN  有线网卡 
    

esp8266wifi模块:  优点 体积小 结构紧凑 使用方便  价格便宜   确定  传输速度不高 
    stm32 uart3  PB10,1P11 --->  esp8266wifi模块 
    通信方式:  串口通信  默认波特率 115200 
    通信指令:  AT 指令 

工作模式:
    Station模式 :  可以链接别人的热点  
    AP :  产生热点 让别人连接 
    ST + AP: 既可以连接别人的热点 也可以 产生热点  
    
AT 指令测试:
    PC USB串口 ----  uart1   stm32 uart3  PB10,1P11 --->  esp8266wifi模块 
    STM32上 写一段 桥接串口的程序:
    
1. 测试指令  
    AT 
    
2.查询wifi模式  
    AT+CWMODE?
    
3.设置wifi模式    
    AT+CWMODE=1
    1,Station模式  2 AP  3 AP+Station模式
    
4. station 模式 查询附近的热点 
    AT+CWLAP
    
    +CWLAP:(4,"智慧星",-66,"12:3a:38:11:e9:1b",1,23,0)
    +CWLAP:(4,"远航星_2.4G",-57,"38:a9:1c:22:c1:d9",1,28,0)

+CWLAP: <ecn>,<ssid>,<rssi>[,<mode>] 

5. 链接某个热点 
    AT+CWJAP=<ssid>,<pwd> 
    AT+CWJAP="XWQPC","12345678"
    
6. 退出热点
    AT+CWQAP

7. station 模式  连接AP 后  连接到 TCP server 
    TCP server:  socket --> bind ---> listen --> acceapt 
    TCP client:  socket --> connect ;
    
PC机 tcp server 服务器 : 网络调试助手 

    单 路 连 接 (+CIPMUX=0)时:

    AT+CIPSTART=<type>,<addr>,<port>    
    AT+CIPSTART="TCP","192.168.137.1",8888

ESP:接收到 服务器发送的消息: 
+IPD,5:hello

ESP:发数据给服务器

获取TCP连接状态指令:
    AT+CIPSTATUS

    单 路 连 接 (+CIPMUX=0)时:
    AT+CIPSEND=<length>
1. AT指令 发送要发送到服务器的 数据长度 
    AT+CIPSEND=5
2. 等待反馈 '>' 然后发送你 要发送的数据 给esp
    
服务器端口  esp 会反馈 CLOSED   
esp 主动断开服务器 
    AT+CIPCLOSE    
    
反之:  ESP 开热点   PC机连接热点   ESP 开TCP server    

STM32程序驱动 esp8266 实现wifi通信: 
实验11:     由单片机构造 AT指令 控制espwifi 
    
    实现 esp 连接 电脑的wifi热点  实现 远程开锁 
配置:     
    1. uart3  收(DMA不定长接收) 发(阻塞发送) at指令 
        启用uart3  启用DAM 接收通道  启用 中断   115200 8n1 
    2. uart1  printf移植 调试输出 
    3. 开锁   PB12   低电平开锁  
    
程序上:
    esp8266串口wifi的驱动程序: 
    实现功能: 
        1) 发送AT指令 到esp 
            HAL_UART_Transmit( &huart3, ...
        2) 等待接收反馈 
            反馈 有可能 成功 "OK"  也可能失败 "..."
        3) 处理响应  判断反馈字符串 是否是成功的判定  
            有可能在 发送完毕 指令后 第一个响应的 不一定是 反馈字符串 
            等待 
        4) 判断 等待是否超时  
            
    参考 esp8266wifi 示例
    
wifi应用: 
    配置wifi: 
        1. esp 热点  名字固定   app 连这个热点  配置  station连接的热点名称和密码 
        2. esp station 模式  连接对应热点  实现功能  
    
freeRTOS: 
    一款开源 免费的 实时操作系统,  多用于单片机 

实时操作系统: 实时性强  多用于控制 
    对某个 触发的操作(中断)  响应时间 固定  

单片机操作系统 与 Linux: 
    单片机操作系统: 代码体积小  K  硬件资源少 RAM   没有任何安全性  单片机也不需要 
        任务管理  多线程  多任务(伪)并发
        内存管理  没有内存虚拟化   将内存当做一个大数组进行管理
    
    若需要文件系统: 则需要额外 添加 fatfs   
        没有设备管理的概念
        
Linux: 系统主件  安全性高    多媒体 
    进程管理 
    内存管理  有内存虚拟化   内核空间  用户空间之分 
    设备管理
    文件系统 
    网络管理 
    
freeRTOS:  9+  10+ 
    UCos  RTthread  VXworks ...

调度器: freeRTOS实现多任务的机制  对任务进行 切入切出的 实体 
    任务: 一个函数  完成一些事情的函数, 其不会退出(return) 可以 delete
    void ATaskFunction( void *pvParameters );
    {
        for(;;){ 做事 }
        vTaskDelete( NULL ); //结束任务
    }
    
    创建任务: 
    
    portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
        const signed portCHAR * const pcName,
        unsigned portSHORT usStackDepth,
        void *pvParameters,
        unsigned portBASE_TYPE uxPriority,
        xTaskHandle *pxCreatedTask );
    xTaskCreate() API 函数原型
    
    pvTaskCode: 线程函数名 
    pcName:     线程描述  字符串 长度有限  可配置 
    usStackDepth: 指定线程 栈空间大小  单位 字(int 4字节)   
    pvParameters: 线程函数的参数 
    uxPriority: 线程优先级  0最低 到 最高优先级(configMAX_PRIORITIES – 1)
    pxCreatedTask: 传出任务的句柄     &任务句柄
    
抢占式RTOS调度器
    优先级高的任务 抢占 优先级低的任务的运行时间(CPU) 
    
协作RTOS调度器        
    任务没有优先级之分, 一个任务主动出让CPU后 另一个任务才可以运行 

时间片调度:  在优先级相同的情况下 每个人获得一个cpu时间片运行 
    依赖于 心跳时钟(硬件定时器中断)    
    
通常FreeRTOS使用 时间片+抢占式RTOS调度器 调度策略 

任务阻塞延时:
    vTaskDelay(心跳时间戳);  //不会耗费CPU 
    osDelay(ms); 
        portTICK_RATE_MS(ms);  // 将ms转换为 心跳时间戳
    
    HAL_Delay(); 

精确延时:   需要 vTaskDelayUntil 打开 
    void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
类似:     
    for(;;)
    {
        do(); //做事 耗费时间x tick
        vTaskDelay(100-x);
    }
    
    
实验12:  stm32 移植freeRTOS  实现 多线程功能
        一个线程播放音乐 
        另一个线程流水灯     

freeRTOS 内存管理: 
    静态区: 除堆区外的 内存   全局变量 静态变量  
    堆区  : 通过宏 TOTAL_HEAP_SIZE 设置堆区内存大小  

???:  5种内存管理方式:heap_[12345].c 
    heap_1.c  只申请 不释放  
    heap_2.c  可以申请和释放  不处理内存碎片问题  使用者少  每次都固定申请相同大小的内存 
    heap_3.c  对标准io中的 malloc free 封装 实现线程安全  
    heap_4.c  可以申请和释放 也可以处理内存碎片(ldle线程处理)    使用多 
    heap_5.c  可以有多个 不连续的堆空间  
    
堆区内存访问:  不能在中断上下文 使用 
    p = pvPortMalloc(n字节); 申请内存 等同于malloc();
    vPortFree(p);    释放内存 等同于free();
    
线程操作API: 
    创建     xTaskCreate()
    删除     vTaskDelete()
    延时阻塞  vTaskDelay()
    挂起   vTaskSuspend()
    唤醒   vTaskResume()
    
    主动出让CPU: taskYIELD()   将时间片剩余时间 出让给其他线程 
    
    关闭调度器  vTaskSuspendAll()     只有当前线程 独占CPU运行 
    恢复调度     xTaskResumeAll()     需要耗费较多CPU时间


练习: 两个线程  都使用printf输出  观察有什么现象?
    
同步互斥相关:  
    同步:  线程之间有序运行  生产者消费者模型  
    
    互斥:  同一时间只能一个线程访问  
    方式1:  优点 简单 之间有效,  中断上下文 也可以使用  
                可以保护中断与线程 间的竞态  也可以 线程和线程间的竞态 
            缺点: 可能使得中断被延后操作  
            保护的临界区, 比较小,  不能阻塞 休眠  
    
        关闭调度器:  关调度器中断  心跳定时器 
            taskDISABLE_INTERRUPTS();
            只会关闭  5-15 优先级的中断  可以使用freeRTOS的 API的中断  FROM_ISR()
            0-4 中断优先级 的 中断  不受freeRTOS管理  在这些中断处理函数中 
                不能使用freeRTOS的 任何 API
            
        打开中断: 
            taskENABLE_INTERRUPTS();
            
        进入临界区:  关中断的嵌套版本  
        taskENTER_CRITICAL();  在线程上下文 进入临界区               处理线程与线程间 以及 中断与线程间
        taskENTER_CRITICAL_FROM_ISR(); //在中断上下文进入临界区       处理中断与中断间的竞态
        
        不能延时 阻塞 休眠  
        
        出临界区: 
        taskEXIT_CRITICAL()
        taskEXIT_CRITICAL_FROM_ISR(); 
    
    方式2: 
        互斥锁:  完成 线程间的 竞态   使用二值信号量完成的 
            优点: 可以在临界区处理耗时操作, 调度器正常调度, 可以阻塞 休眠  中断可以被处理 
            缺点: 不能处理 中断与 线程间的 竞态问题 
            
freeRTOS 信号量相关API: 
    
    二值信号量:  取值 0-1 
    vSemaphoreCreateBinary();
    
    互斥锁: 就是初始值为1 二值信号量  
    xSemaphoreCreateMutex();
    
    计数信号量:  取值 0-n 之间 可以配置     
    xSemaphoreCreateCounting();   //cubemx 默认不使用   配置 USE_COUNTING_SEMAPHORES == enable
    
    xSemaphoreGive();  // 释放信号量  ++   不会阻塞 
    xSemaphoreGiveFromISR(); // 在中断上下文使用
    
    xSemaphoreTake();  // 消耗信号量  --   当信号量不足 ==0 阻塞等待 
    xSemaphoreTakeFromISR(); // 在中断上下文使用 
    
    uxSemaphoreGetCount(); // 得到信号量的数值 而不消耗 

作业: 两个线程  一个线程 控制蜂鸣器  一个线程 控制LED  
    要求 实现 一个音符响  就 LED 闪一次 50ms
    
    同步: 
    
printf输出的 原子性 
//实现 osprintf()
void osprintf(const char *fmt, ...)
{
    char buf[100] = {0};
    int len;
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf)-1, fmt, ap);
    va_end(ap);
    len = strlen(buf);
    
    if( __get_IPSR() == 0)
    {//没有在中断中
        xSemaphoreTake( mutex, portMAX_DELAY );
    }
    
    HAL_UART_Transmit(&huart1,(uint8_t*)buf,len,100);
    
    if( __get_IPSR() == 0)
    {//没有在中断中
        xSemaphoreGive( mutex );
    }
}

freeRTOS中 线程间通信 或 中断与线程间通信  

    线程间通信: 
        1. 全局变量  + 互斥锁    可以传递大量数据 速度快  
        2. 信号量    二值信号量(01)  计数信号量(cnt)
        3. 消息队列 

队列使用:  重要, freeRTOS中  信号量互斥锁等都是依靠队列实现的 
    1. 队列本身 是实现了原子性操作的  

    1. 创建队列   使用队列前 需要先 
    QueueHandle_t 队列句柄 = xQueueCreate() 
    
    2. 发送数据到队列中 
    xQueueSendToBack() / xQueueSend()  发送数据到队列尾部 
    xQueueSendToFront()   发送数据到队列头部  插队 
    在中断中 使用 以下两个 
    xQueueSendToFrontFromISR()
    xQueueSendToBackFromISR()

    3. 从队列中 接收数据 
    xQueueReceive()   接收队列头部的 一个数据  接收完成后 删除队列头数据 
    xQueuePeek()       接收队列头部的 一个数据  接收完成后 不删除头部数据  
            主要用于 测试一下队列头部的数据 
    xQueueReceiveFromISR()  中断中接收数据 
    xQueuePeekFromISR()     
    
    4. 获得队列中 有效数据的个数 
    uxQueueMessagesWaiting()
    uxQueueMessagesWaitingFromISR()
    
    5.删除队列 
    vQueueDelete();
    
    6. 清空队列 
    xQueueReset();
    
示例:  
    线程1:  通过队列 向线程2 发送一个字符串   指针队列   申请堆区内存 
    线程2:  读队列 将这个字符串 的指针  打印出来  释放堆区内存

邮箱模式: 
    使用队列传递  不存在竞态问题的指针   常量  堆区指针  
    
中断延迟处理: 中断和线程的通信  队列 
    实现 按键点击    LED 闪烁3次   
    
实现方式:
    1. 线程休眠与激活   丢失中断 当中断触发较快时
    2. 中断触发后  向队列发送数据  LED线程 接收队列数据处理 
    
中断消抖: 
    使用软件定时器 实现 
    
    定时器回调函数: 
    void vTimerCallback( TimerHandle_t xTimer )
    
    
    1. 创建一个软件定时器 
    TimerHandle_t xTimerCreate( const char *pcTimerName,    // 定时器名 字符串 
        const TickType_t xTimerPeriod,   // 超时时间  以心跳时间为单位
        const UBaseType_t uxAutoReload,  // 是否自动 重复触发  
        void * const pvTimerID,          // 传递给软件定时器回调函数的一个参数 
        TimerCallbackFunction_t pxCallbackFunction );  // 定时器回调函数  
    
    2. 删除定时器 
    xTimerDelete(); 
    
    3. 定时器启动与停止
    xTimerStart();
    xTimerStartFromISR();
    
    xTimerStop();
    xTimerStopFromISR(); 
    
    4. 重置定时器  重置到期时间  
    xTimerReset();
    
    5. 判断定时器是否启动 
    xTimerIsTimerActive();  // 没有中断版本, 不能在中断中使用 
    
    6. 重新给定定时器超时时间 
    xTimerChangePeriod();
    xTimerChangePeriodFromISR();
    
freeRTOS软件定时器实现原理 如图所示:
    
    示例:  使用软件定时器 实现1S闪灯 
    
freeRTOS 栈区调试:
    
    freeRTOS每个线程 都有独立栈区, 通常栈区从堆区分配 
    线程函数中: 创建局部变量  调用函数  会消耗该线程的栈区 
    线程栈溢出: 线程运行过程中 栈耗尽 导致溢出   死机
    
    1. 获得当前堆内存 剩余多少  
    剩余堆内存 单位字节 = xPortGetFreeHeapSize()
    
    2. 获得指定线程 的 栈剩余内存是多少  需要配置 宏  uxTaskGetStackHighWaterMark = enable
    当前线程剩余栈空间 单位字 = uxTaskGetStackHighWaterMark(NULL);
    指定线程 当前栈剩余空间  = uxTaskGetStackHighWaterMark(指定线程句柄);
    
    3. 栈溢出钩子函数  重写钩子函数   配置 CHECK_FOR_STACK_OVERFLOW 宏指定 栈侦测方式1/2
    void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName )
    {
        当该函数被调用时  栈发生了溢出 
    }
    
    
钩子函数: 回调函数 当某种条件发生时 freeRTOS会调用对应钩子函数
    栈溢出钩子: 
        void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName )
        
    空闲任务钩子函数: 当空闲任务运行时,该钩子函数会被调用
        void vApplicationIdleHook( void ); // 延时休眠不能做    耗时任务可以
    
    心跳钩子函数:  当心跳定时器中断触发 该钩子函数就会被调用 
        void vApplicationTickHook( void ); // 延时休眠不能做  耗时操作 也不能做 
    
    堆内存满 钩子函数
        void vApplicationMallocFailedHook( void );
    
    
    
    
    
    
    


    
 

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值