首页 > 其他分享 >从0开始的STM32开发之旅——TM1637数码管

从0开始的STM32开发之旅——TM1637数码管

时间:2024-11-28 19:30:56浏览次数:8  
标签:disp DIO handle CLK STM32 数码管 IIC TM1637 TIM1637

目录

前言

数码管介绍

一般数码管的显示原理

编写驱动第一步——观察我们的TM1637

编写驱动第二步——查看如何发起通信

第三步——封装协议层

bonus:亮度设置

第四步:上升为业务逻辑

第五步:构建demo示例


前言

在知识内容上,他算是软件IIC模拟的一个例子,所以实际上也算是使用GPIO来驱动外设的demo。但是这里打算为后面的驱动协议篇打下一个比较好的开始。所以笔者在这里打算写一篇使用GPIO来模拟驱动协议的博客笔记。

当然,程序中出现的结构体是笔者自己的封装代码。如果您成功跑通了示例驱动了数码管,但是想要让原先的程序不要那么史山,可以查看本教程随之进行封装。

笔者给的示例代码是跑在STM32C8T6上,只需要将时钟设置为72MHZ即可,然后使用的是CubeMX生成自动代码+PlatformIO完成的工程创建和书写。具体的demo在:https://github.com/Charliechen114514/MCU_Libs/tree/main/TM1637

数码管介绍

嗯,笔者第一次对数码管有认知是在数码管时钟的地方。你看到的这个

这就是一个典型的数码管闹钟。可以看到这里的数码管是4位的。我们下面需要为之写驱动的TM1637也是四位的数码管。

一般数码管的显示原理

为了节约端口,数码管的每个LED的两端分别连接电极,其中一个电极与对应的数码管引脚相连,另一个电极连接到公共端(共阳或共阴)。数码管有两种常见的连接方式:

  • 共阳数码管:所有的LED的阳极(正极)连接在一起,控制信号通过控制各个LED的阴极(负极)来选择显示的内容。

  • 共阴数码管:所有的LED的阴极(负极)连接在一起,控制信号通过控制各个LED的阳极(正极)来选择显示的内容。

我们的TIM1637使用的都是共阴极数码,这点是需要我们注意的。

想要让我们的数码管显示数字,就需要驱动对应的管子亮起来。比如说,如果我们想要点亮管子显示0,那么,我们就需要让a~f的管子亮起来,也就是abcdef。使用进制表达就是0x3F(gfedcba = 0111 1111)。如果是共阳管,只需要取反即可(0xC0)

我们当然可以这样继续列写得到一个表:

显示的数字点亮的LED十六进制 (共阳极)十六进制 (共阴极)
0abcdef0xC00x3F
1bc0xF90x06
2abdeg0xA40x5B
3abcdg0xb00x4F
4bcfg0x990x66
5acdfg0x920x6D
6acdefg0x820x7D
7abc0xF80x07
8abcdefg0x800x7F
9abcdfg0x900x6F
Aabcefg0x880x77
Bcdefg0x830x7C
Cadef0xC60x39
Dbcdeg0xA10x5E
Eadefg0x860x79
Faefg0x8E0x71

笔者后面的驱动中会给出更多的字符。先不要着急。

编写驱动第一步——观察我们的TM1637

TM1637是这样的一个四位数码管,如果您在淘宝或者其他电子器件购买的话,看到的器件是长这样的:

一共四个管脚,VCC, GND,CLK和DIO。在板子的背面上可以从丝印上读取到这些信息。我们查阅TM1637的手册。可以看到这样的一段话:

微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变数据输入的开始条件是 CLK为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。 TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。

TMD,熟悉IIC的人都会知道这个就是类似于IIC的玩意,但是又跟IIC有所不同,所以只能自己写软件模拟上面的协议。这是因为:

  1. 该总线传输位的顺序与iic相反

  2. 该设备独占该总线(没有设备地址可言)

所以,我们封装这样的结构体作为我们操作的handle

typedef struct {
    /* CLK(SCK) */
    CCGPIO*  CLK;
    CCGPIO*  DIO;
}CCTIM1637_Handle;

其中CCGPIO是笔者之前封装过的GPIO驱动库。STM32 从0开始系统学习4 编写LED驱动_specifies the pull-up or pull-down activation for -CSDN博客。这里不做赘述。后面的代码会放出来。

这下看懂了,所以,我们要做的就是手搓协议了。

编写驱动第二步——查看如何发起通信

我们就是采用如上的办法自动的传递我们写入的数据。流程就是:发起通信,等待应答,结束通信。

第一步就是发起一次通信:

static void TIM1637_IIC_Start(CCGPIO* CLK, CCGPIO* DIO)
{
    __fast_set_dio_gpio_out(DIO);
    setGPIOOn(CLK);
    setGPIOOn(DIO);
    delay_us(2);
    setGPIOOff(DIO);
}

首先就是复原时钟线和数据线,为DIO被拉低做准备。因此,需要设置GPIO的模式是:

// 0011输出模式   1000上下拉输入模式
static void inline __fast_set_dio_gpio_out(CCGPIO* DIO){
    DIO->letter_case->CRL &= 0X0FFFFFFF;        # 清空原先的GPIO模式
    DIO->letter_case->CRL |= (uint32_t)3<<28;   # 设置为输入
}

这就是快速的调整我们的GPIO的输出输入模式从而加快通信的速度,毕竟使用HAL库做一大堆设置然后commit我们的settings还是有点抽象。

现在我们等待2us,就可以拉低我们的DIO的线了。这个是手册的示例得知的。

应答部分如下:(吐槽,手册的是想写ACK写成了ASK了)

static void TIM1637_IIC_ACK(CCGPIO* CLK, CCGPIO* DIO)   // 看我们的TM1637活没活,实际上这里去除了应答检测部分,因为很多人测试标识实际上没有做应答。所以这里笔者直接去掉了,或者进一步提升效率甚至可以不理会应答部分!
{
    __fast_set_dio_gpio_in(DIO);
    setGPIOOff(CLK);
    delay_us(5);
    // while(gainGPIOState(DIO));
    setGPIOOn(CLK);
    delay_us(2);
    setGPIOOff(CLK);
}

停止通信,也是如法炮制。

static void TIM1637_IIC_Stop(CCGPIO* CLK, CCGPIO* DIO)
{
    __fast_set_dio_gpio_out(DIO);
    setGPIOOff(CLK);
    delay_us(2);
    setGPIOOff(DIO);
    delay_us(2);
    setGPIOOn(CLK);
    delay_us(2);
    setGPIOOn(DIO);
}

有趣的是这些参数delay的秒数不是看手册而是看示例。总而言之,应该能跑。

第三步——封装协议层

现在我们就可以封装协议层了。按照手册,我们一次是发送一个byte。那么,就要请出我们常见的writebyte方法了:

static void TIM1637_writeByte(CCGPIO* DIO, CCGPIO* CLK, uint8_t byte)
{
    // 调用此函数需要通信已经开始 
    __fast_set_dio_gpio_out(DIO);
    for(uint8_t i = 0; i < 8; i++){
        setGPIOOff(CLK);            // 拉低CLK
        // 结合手册,只有拉低CLK的时候才允许读写
        setGPIOState(DIO, (byte & 0x01 ? ON : OFF));
        delay_us(3);
        byte >>= 1;
        setGPIOOn(CLK);             // 拉高CLK
        delay_us(3);
    }
}

笔者的writeByte函数比较干练。实际上就是依次的移位将低字节的比特送到寄存器中写出去给TM1637。可以查看笔者给出的时序图来搞明白为什么上下电平等。

现在我们就可以传送比特了。比如说我想要显示一个数字:

  1. 发起通信TIM1637_IIC_Start

  2. 写数据到显示寄存器 40H 地址自动加1 模式,也就是说让我们设置成只管卡卡写就行的模式(也就是送入0x40这个比特)TIM1637_writeByte

  3. 等待应答TIM1637_IIC_ACK

  4. 结束通信TIM1637_IIC_Stop

  5. 再次发起通信TIM1637_IIC_Start

  6. 告知地址命令设置 显示地址 00H,也就是送入比特0xC0(1100 0000)TIM1637_writeByte

  7. 等待应答TIM1637_IIC_ACK

  8. 结束通信TIM1637_IIC_Stop

  9. 发起通信准备写入显示数据TIM1637_IIC_Start

  10. 写入对应目标的魔数显示码后(TIM1637_writeByte)等待应答,完成应答后(TIM1637_IIC_ACK)继续写入数据。直到写完为止

  11. 结束通信TIM1637_IIC_Stop

所以,程序看起来就是这个样子的:

/*
    handle是上面封装的结构体指针
    arr是显示arr的
*/
void commit_display_auto_upgrade(CCTIM1637_Handle* handle, const DisplayArray arr)
{
    TIM1637_IIC_Start(handle->CLK, handle->DIO);
    TIM1637_writeByte(handle->DIO, handle->CLK, 0x40);
    TIM1637_IIC_ACK(handle->CLK, handle->DIO);
    TIM1637_IIC_Stop(handle->CLK, handle->DIO);
​
    TIM1637_IIC_Start(handle->CLK, handle->DIO);
    TIM1637_writeByte(handle->DIO, handle->CLK, 0xC0);
    TIM1637_IIC_ACK(handle->CLK, handle->DIO);
​
    for(uint8_t i = 0; i < SUPPORT_DISP_N; i++){    // 这里是6位(看手册),所以写入6
        TIM1637_writeByte(handle->DIO, handle->CLK, arr[i]);
        TIM1637_IIC_ACK(handle->CLK, handle->DIO);
    }
​
    TIM1637_IIC_Stop(handle->CLK, handle->DIO);
}

到这里,我们的基本的操作就已经完成了。

bonus:亮度设置

手册里给出了亮度设置的表格:

可以看到,我们可以这样设置我们的亮度:

void    set_TIM1637_Brightness(CCTIM1637_Handle* handle, uint8_t level)
{
    TIM1637_IIC_Start(handle->CLK, handle->DIO);
    // 高八位总是1000 第八位中的第一位是开,后面就是0~7级别的level,置位法秒了
    TIM1637_writeByte(handle->DIO, handle->CLK,  0x88 | level );
    TIM1637_IIC_ACK(handle->CLK, handle->DIO);
    TIM1637_IIC_Stop(handle->CLK, handle->DIO);
}

下面就是我们的上层软件封装时间。

第四步:上升为业务逻辑

我们关心的是自己显示的数组的大小与需要的mapping映射表,我们的handle还有我们的小数点的问题。查看示例就会发现他是这样处理我们的小数点的:就是直接对着我们的第二位与上我们的0x80。这里给出可移植的代码:

#pragma once
​
/*
    This file is gonna support the Params of TIM1637
*/
​
/*
    TIM1637 Support Display 6 bits, the array size shell be used
    Support settings in building :)
*/
#ifndef __SUPPORT_DISP_N
#define SUPPORT_DISP_N  (6) 
#else
#define SUPPORT_DISP_N  __SUPPORT_DISP_N
#endif
​
/*
    COLON的下标附属,这里就是对第一位下标直接上 | 操作
*/
#ifndef __COLON_INDEX_N
#define COLON_INDEX_N (1)
#else
#define COLON_INDEX_N __COLON_INDEX_N
#endif
​
#if COLON_INDEX_N >= SUPPORT_DISP_N
#error  "Error in setting colon index! index are overflowed!"
#endif
​
#define COLON_DISP_MAGIC 0x80
​
#define SET_COLON(DISP_ARR) do{DISP_ARR[COLON_INDEX_N] |= COLON_DISP_MAGIC;}while(0)
#define OFF_COLON(DISP_ARR) do{DISP_ARR[COLON_INDEX_N] &= ~COLON_DISP_MAGIC;}while(0)
​
/* Minimize the includings */
typedef unsigned char uint8_t;
typedef uint8_t DisplayArray[SUPPORT_DISP_N];
​
/*
    Followings are struct Tables
*/
typedef struct{
    uint8_t char_;
    uint8_t disp_magic;
}TIMDisplaySrcPair;
​
// 这个是查询魔数,也就是说,我们给定一个1, 他能返回显示1的显示数据。
#define FAILED_SEARCH       (-1)
uint8_t search_display_one(uint8_t wanna);

实际上需要实现的部分只有search_display_one。所以:

#include "tim1637_params.h"
​
static const TIMDisplaySrcPair DisplaySources[] = {
        {0,     0x3F},
        {1,     0x06},
        {2,     0x5B},
        {3,     0x4F},
        {4,     0x66},
        {5,     0x6D},
        {6,     0x7D},
        {7,     0x07},
        {8,     0x7F},
        {9,     0x6F},
        {'0',     0x3F},
        {'1',     0x06},
        {'2',     0x5B},
        {'3',     0x4F},
        {'4',     0x66},
        {'5',     0x6D},
        {'6',     0x7D},
        {'7',     0x07},
        {'8',     0x7F},
        {'9',     0x6F},
        {'a',     0x77},
        {'b',     0x7C},
        {'c',     0x58},
        {'d',     0x5E},
        {'e',     0x79},
        {'f',     0x71},
        {'h',     0x76},
        {'l',     0x38},
        {'n',     0x54},
        {'p',     0x73},
        {'u',     0x3E},
        {'A',     0x77},
        {'B',     0x7C},
        {'C',     0x39},
        {'D',     0x5E},
        {'E',     0x79},
        {'F',     0x71},
        {'H',     0x76},
        {'L',     0x38},
        {'N',     0x54},
        {'P',     0x73},
        {'U',     0x3E},
        {' ',     0x00}
};
​
#define SRC_SIZE    (sizeof(DisplaySources)/sizeof(DisplaySources[0]))
​
uint8_t search_display_one(uint8_t wanna)
{
    for(int each_one = 0; each_one < SRC_SIZE; each_one++){
        if(wanna == DisplaySources[each_one].char_){
            return DisplaySources[each_one].disp_magic;
        }
    }
    return FAILED_SEARCH;
}

第五步:构建demo示例

笔者使用的是STM32C8T6跑的demo,我写的扩展库是相对架构无关的,不需要在CubeMX设置任何东西,代码里面直接开启就好。

void config_my_tim(CCGPIO* CLK, CCGPIO* DIO){
    createGPIO(CLK, GPIOB, GPIO_PIN_6, 'B');
    createGPIO(DIO, GPIOB, GPIO_PIN_7, 'B');
    CCGPIOConfig config;
    createGPIOConfig(&config, GPIO_SPEED_FREQ_HIGH, GPIO_NOPULL, GPIO_MODE_OUTPUT_PP);
    setGPIOConfig(CLK, &config);
    setGPIOConfig(DIO, &config);
}
​
void config_my_tim2(CCGPIO* CLK, CCGPIO* DIO){
    createGPIO(CLK, GPIOA, GPIO_PIN_6, 'A');
    createGPIO(DIO, GPIOA, GPIO_PIN_7, 'A');
    CCGPIOConfig config;
    createGPIOConfig(&config, GPIO_SPEED_FREQ_HIGH, GPIO_NOPULL, GPIO_MODE_OUTPUT_PP);
    setGPIOConfig(CLK, &config);
    setGPIOConfig(DIO, &config);
}
​
void adjust_num(uint8_t disp[]){
  // the two and third one is seconds
  disp[3]++;
  if(disp[3] >= 10){
    disp[2] += 1;
    disp[3] = 0;
  }
​
  if(disp[2] >= 6)
  {
    disp[1] += 1;
    disp[2] = 0;
  }
​
    if(disp[1] >= 10){
    disp[0] += 1;
    disp[1] = 0;
  }
}
​
/* USER CODE END 0 */
​
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  ... // 省略自动生成的代码
  DisplayArray arr;
  CCGPIO CLK, DIO, CLK2, DIO2;
  CCTIM1637_Handle handle, handle2;
  config_my_tim(&CLK, &DIO);
  config_my_tim2(&CLK2, &DIO2);
  init_cctim1637(&handle, &CLK, &DIO);
  init_cctim1637(&handle2, &CLK2, &DIO2);
  set_TIM1637_Brightness(&handle, 7);
  set_TIM1637_Brightness(&handle2, 3);
  uint8_t shell_disp = 1;
  uint8_t disp_num[4] = {0, 0, 0, 0}; 
  while (1)
  {
    /* USER CODE END WHILE */
​
    /* USER CODE BEGIN 3 */
    set_display_array_at_index(disp_num[0] , arr, 0);
    set_display_array_at_index(disp_num[1], arr, 1);
    set_accuired_disp_colon(arr, shell_disp);
    set_display_array_at_index(disp_num[2], arr, 2);
    set_display_array_at_index(disp_num[3], arr, 3);
    commit_display_auto_upgrade(&handle, arr);
    commit_display_auto_upgrade(&handle2, arr);
    HAL_Delay(1000);
    shell_disp = !shell_disp;
    adjust_num(disp_num);
  }
  /* USER CODE END 3 */
}

效果图:

完整的工程示例在这里:MCU_Libs/TM1637 at main · Charliechen114514/MCU_Libs · GitHub

标签:disp,DIO,handle,CLK,STM32,数码管,IIC,TM1637,TIM1637
From: https://blog.csdn.net/charlie114514191/article/details/144118516

相关文章

  • STM32 实现流水灯(基于 STM32F103C6T6 + HAL 库)
    一、硬件准备1.STM32F103C6T6A+STLink仿真器2.LED3只3.杜邦线若干(20条以内)4.面包板一块5.立式微动开关2只二、线路连接1.STM32核心板PA4PA5PA6分别接三只LED2.PB5PB6分别接两个开关一端3.保证所有开关、LED负极与核心板共地三、端口配置1.P......
  • 2-STM32F103+ESP8266+EC800K(移远4G Cat1)--整体运行测试-Android和微信扫码绑定EC800
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ZLIOTB/EC800K/my.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p> 说明(这节使用最新......
  • STM32cubeMX配置FreeRTOS生成代码--完成一个简单测试(Led闪烁和向串口发送“hello!world
    一、STM32cubeMX中相关配置(首先我用的STM32板子是STM32F103VBT6,板子不同,配置会略有不同,仅作参考!)打开STM32cubeMX,新建工程,选择对应板子型号:1.配置微控制器的时钟系统HighSpeedClock(HSE):高速时钟源,这里选择的是“Crystal/CeramicResonator”,意味着使用外部晶体......
  • STM32之串口232通讯
    STM32F407系列文章-RS232通讯(六)文章目录前言一、串口(UART)二、RS23-硬件特性三、RS232-程序实现1.函数rs232_init()2.函数USART_UX_IRQHandler()3.函数rs232_send_data()4.函数rs232_receive_data()5.函数rs232_receive_data()总结前言一般STM32F407芯片都会......
  • STM32——GPIO
    目录一、基础知识      (一)基本特性                        数字和模拟信号                        IO端口基本结构      (二)引脚配置       多路复用——基于stm32f103       引......
  • 26 基于STM32的智能门禁系统(指纹、蓝牙、刷卡、OLED、电机)
    目录一、主要功能二、硬件资源三、程序编程四、实现现象一、主要功能基于STM32单片机,六个按键,分别代表指纹、蓝牙、刷卡的正确进门与错误进门;比如第一个按键按下,表示指纹正确,OLED显示指纹正确,开门,第二个按键按下的话,则指纹错误,OLED显示指纹错误,请重试,第二个按键连......
  • 51单片机入门:数码管(3)
    数码管简介数码管每段其本质就是个LED灯,只需要控制特定的LED灯亮就能显示数据。普中开发版所使用的是两个并在一起共阴极连接的“4位数码管”,可以同时显示8个数字。数码管的显示可以分成静态显示和动态显示,这里先介绍最简单的静态显示。数码管分为共阴极连接和共阳极连接,顾名......
  • 七段数码管绘制
    fromturtleimport*fromtimeimport*defdrawLine(draw):#回执单段数码管pendown()ifdrawelsepenup()fd(40)right(90)defdrawDigit(d):#根据数字绘制七段数码管drawLine(True)ifdin[2,3,4,5,6,8,9]elsedrawLine(False)drawL......
  • STM32和STM8开发工具、常用软件和开发环境汇总
    文章目录一、前言二、KeilC51软件三、KeilMDK-ARM四、STM32CubeMX及HAL库五、STM32CubeIDE六、STM8CubeMX七、STM32ST-LINKUtility一、前言整理一些常见的STM32/STM8开发所需要的安装包和工具。可以分别去官网下载最新的安装包。也可以通过关注【小康师兄】......
  • STM32 cubeIDE 可执行文件 *.elf 的烧录
    1.准备工具:ST_LINKST-Link/V2 JTAG/SWD标准的接口排列:2.用杜邦线连接单片机3.开始烧录方法1:Ctrl+B编译项目成功后,右键项目名-Runas- STM32 C/C++Application方法2:Ctrl+B编译项目成功后,点击RunDebug按钮,直接烧录。当有多个程序时,默认烧写最近烧录过的程序,点击......