赞
踩
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 );
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。