从零开始的STM32之旅

Pluviophile 发布于 2024-11-27 137 次阅读


碎碎念

好久都没有来写过东西了。。。开学以来真的都好忙好忙,每天都连轴转,,做不完的项目,上不完的课,还有各种奇奇怪怪的任务。。。好忙好忙

0.在一切开始之前

首先,请坐和放宽。摆正心态,相信自己能学会。保证一定的耐心,以沉下心拿出一定的时间去学习某段知识。

1.前言

有很多人问我stm32应该怎么学习,这些人大部分是学过51转过来的,看到32的示例代码,可能会怀疑:"这还是c语言吗?"感觉太复杂而无从下手。就算过一遍江协的教程依然是云里雾里,上手做项目不知道怎么开始。

我的观点是:从实用项目入手,用什么学什么,在实践中逐渐构建起知识体系。
不用担心知识学不全,因为知识都是环环相扣的。比如我想用oled显示当前的室温,是不是听上去很麻烦很难?其实这只涉及到两个模块:oled和温度传感器,从oled就可以延伸出IIC、SPI通信协议、gpio模式配置等等这些,温度传感器如果选用DHT11这种单线iic的模块,就会延伸到通信时序、对数据的编解码、c语言字符串、数组,如果选用输出模拟信号的DS18B20这种,就会涉及到数模转换、ADC校准、芯片内部电压等等这些,可以看到,这些需要的知识是环环相扣的,最终都指向最底层最基础的部分。可以说:

高级的知识就是基础的知识的变形运用。

而一大堆函数、指针、结构体、不知所云的变量,虽然看起来唬人,但我们也要知道长城不是一天建成的,一点一点去理解,总有一天我们也能学会。这就是耐心的重要性了。

闲聊就到这里。

2.准备工作

你需要的:

  • 一块stm32(f103c8t6)、一部电脑、面包板、杜邦线等基础设施
  • 清醒的头脑

3.从基础项目开始

就拿上面的用oled显示当前的室温来开始吧。选用DHT11作为传感器,这个传感器同时可以检测当前的相对湿度,可以一并用oled展示。

12.9更新:

1.OLED

首先是OLED显示部分。这部分涉及到的知识有iic通信协议(作为主机),由于复用性过强这里就不细讲了,从江协的教程1里学就可以。看这些视频时不要看到列表里一大堆外设就望而却步,还是记住用什么学什么,这些模块之间的关联性并不强,前面的视频中铺垫的基础知识可以自己去学。

OLED的原理可以看这篇博客:https://www.cnblogs.com/Ethan-Code/p/16783114.html

从哪里才能找到这种博客呢?

百度一搜全都是。但更推荐用google 。怎么用参见往期文章2
说是推荐上github去看成熟的项目,但这种完成度高的项目一般都只会给出部分用法,真正的实现是不会一步步教你的,这对于初学者来说可能理解起来有点困难,所以上来还是先看博客上的教程比较好,推荐博客园。

csdn和什么腾讯云开发者巴拉巴拉的都太垃圾了,不要用。
gitee这种只作为国内加速用,也不要依赖。

但了解SSD1306作为主控的OLED的时序后我们可以发现,它使用的IIC协议并非常规的IIC,即第一个字节使用7位地址码加一位读写标志码,而是第一个字节发送从机地址,后面的字节分别写入命令或数据。也就是说它仅仅只是用了IIC通信协议的物理层

将线接好,便可以开始初始化stm32的引脚。其实总的来看,stm32标准库的编程大多为公式化,使用外设时先开启对应时钟、声明初始化结构体、配置初始化结构体、将结构体交给初始化函数等等等等。只不过相关参数配置比较繁琐,容易出错,需要我们细心一些。

如果OLED接了5V的供电,那么需要找到stm32相应的耐压5V管脚(在管脚定义图上表明了"FT"的),否则可能有烧坏的风险。。

IIC协议要求配置引脚为开漏输出,外接上拉电阻,OLED板载已经有了上拉电阻,所以我们只需将引脚配置为开漏就好。

-----------------分割线-------------------

2.DHT11

配置好OLED之后,就是我们的外设DHT11了。先来看一下它的手册里写的时序:

此图片的 alt 属性为空;文件名为 1733716874-image.png

可以看到这个单线IIC协议需要stm32作为从机接收数据,但是需要对DHT11发送一个50us低电平的起始信号,观察DHT11模块我们可以发现在信号线上是有一个上拉电阻的(IIC协议的模块一般都板载上拉电阻),我们只需要把引脚配置为开漏输出,把电平拉低即可,然后再将引脚配置为浮空输入,接受DHT11发来的数据。

那么如何手搓一个IIC接收数据的时序呢?我们先来分析一下它的时序,发现逻辑0的拉高时间比1短,且拉高前都有一段相同的拉低时间,那么我们可以为GPIO配置下降沿中断,在中断中延时78-120us,再次读取GPIO的电平,如果为低那么数据就是0,此时为下一个数据位的前序拉低状态,如果为高那么数据即为1,那么再使用while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin_x) == 1)延时直到GPIO被拉低后退出中断。注意while里要写点延时函数,不然看门狗会叫。
这里用到了外部中断,需要配置EXTI来处理中断。

再然后就是存储数据,在DHT11请求前将温度、湿度等数据置0,在中断函数中依次进行*=2,+=数据即可,这是c语言中最简单的将2进制转为十进制的方法。

获取数据后我们便可以使用OLED显示出来了。

3.定时器

将DHT11配置好后,我们需要每隔几秒将数据更新一下,如果把这个任务放到主程序中执行未免太不优雅了,于是我们可以使用定时器中断,配置RCC、TIM、NVIC,由于定时器中断是内部中断,所以不用配置EXTI。

将定时器中断配置为2s触发,中断函数中执行DHT11的请求函数即可。

这样我们就配置好了一个定时更新并显示的温湿度监测器。

4.代码

只写了基于ESP32的ESP-IDF实现。代码如下,逻辑供参考。

//DHT11.c
#include <stdio.h>
#include "DHT11.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "OLED.h"
#include "LED.h"
#define DHT_IO 0

static void DHT_Start(void);
static void DHT_Get_Data(DHT *a);
DHT DHT_Data, *DHT_Structure;
extern TaskHandle_t led_handle, DHT_Handle;
extern void Beep(uint8_t flag);

void DHT_Sensor_Init(void)
{
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD;
    io_conf.pin_bit_mask = 1ULL<<DHT_IO;
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);
}

void DHT_TaskHandle(void *pvParameters)
{   
    OLED2_ShowIcon(3, 1, 5);
    OLED2_ShowChar(3, 5, '.');
    OLED2_ShowChar(3, 7, 95 + ' ');
    OLED2_ShowIcon(3, 10, 4);
    OLED2_ShowChar(3, 14, '.');
    OLED2_ShowChar(3, 16, '%');
    while(1)
    {
        DHT_Structure = Get_Temp_Humi();
        OLED2_ShowNum(3, 3, DHT_Structure->temperature_h, 2);
        OLED2_ShowNum(3, 6, DHT_Structure->temperature_l, 1);
        OLED2_ShowNum(3, 12, DHT_Structure->humidity_h, 2);
        OLED2_ShowNum(3, 15, DHT_Structure->humidity_l, 1);
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

DHT *Get_Temp_Humi(void)
{
    DHT_Start();
    esp_rom_delay_us(180);
    DHT_Get_Data(&DHT_Data);
    return &DHT_Data;
}

static void DHT_Start(void)
{
    gpio_set_level(DHT_IO, 0);
    vTaskDelay(20 / portTICK_PERIOD_MS);
    gpio_set_level(DHT_IO, 1);
    esp_rom_delay_us(15);
}

static void DHT_Get_Data(DHT *a)
{
    uint8_t temp[40];
    uint16_t jishi = 0;
    vTaskSuspend(led_handle);
    for(int i = 0; i < 40; i++)
    {
        while(gpio_get_level(DHT_IO) == 0)
        {
            esp_rom_delay_us(1);
        }
        esp_rom_delay_us(40);
        temp[i] = gpio_get_level(DHT_IO);
        while(gpio_get_level(DHT_IO) == 1)
        {
            esp_rom_delay_us(1);
            jishi++;
            if(jishi > 1000)
            break;
        }
    }
    vTaskResume(led_handle);
    uint8_t flag = 1;
    if(temp[24] == 1)
    {
        flag = -1;
        temp[24] = 0;
    }

    uint8_t data[5];

    for(int i = 0; i < 5; i++)
    {
        uint8_t num = 0;
        for(int j = 0; j < 8; j++)
        {
            num = num * 2 + temp[i*8+j];
        }
        data[i] = num;
    }
    if(data[4] == data[0] + data[1] + data[2] + data[3])
    {
        data[2] *= flag;
        a->temperature_h = data[2];
        a->temperature_l = data[3];
        a->humidity_h = data[0];
        a->humidity_l = data[1];
    }
}
//DHT11.h
#ifndef __DHT_11_H
#define __DHT_11_H

typedef struct 
{
    int8_t temperature_h;
    int8_t temperature_l;
    uint8_t humidity_h;
    uint8_t humidity_l;
} DHT;

void DHT_Sensor_Init(void);
DHT *Get_Temp_Humi(void);
void DHT_TaskHandle(void *pvParameters);
void Temperature_Warning(void *pvParameters);

#endif

4.总结

回头去看,发现并没有涉及到很多的知识,有的只是引脚配置、时序适配、定时器中断、外部中断这些常规的东西。
都是可以在网上搜到的。至于C语言基础不会的可以去重修了。

其实51也好,stm32也好,都只是作为我们学习的一个媒介,一种路径。重要的是在这个过程中我们逻辑思维的养成,在学习中不止学到知识,也能学到学习的方法,锻炼自己的思维。最后代码这部分虽然我也有懒得写的成分在里面,但更多还是觉得这种事情应该自己去做,抄一份也好。从各种渠道获取知识也是学习的一部分。在网络上比我这个教程简陋的多的是,还有一大堆不知所云的ai批发的文章(好吧又在比烂),想要看懂的话必须到处去搜索。这样在不断搜索碰壁的过程中我们才能找到真正适合自己的学习方法。

  1. 【STM32入门教程-2023版 细致讲解 中文字幕】 ↩︎
  2. 教程在这里 ↩︎