找回密码
立即注册
搜索
热搜: 活动 交友 discuz
发新帖

116

积分

0

好友

12

主题
发表于 2023-9-5 15:28:46 | 查看: 508| 回复: 0 IP:广东省东莞市 电信
本帖最后由 admin3 于 2023-9-5 15:28 编辑

>  本帖最后由 admin3 于 2023-9-5 08:32 编辑


>  本帖最后由 admin3 于 2023-9-3 20:36 编辑


单片机中的ADC(Analog-to-Digital Converter)模块用于将模拟信号转换为数字信号,以便微控制器能够对其进行处理和分析。ADC模块可以将外部的模拟信号转换为数字信号,使得单片机能够对这些信号进行处理。常见的模拟信号包括温度、光强、压力等。

ADC可以将连续的模拟信号转换为离散的数字信号,即进行数据量化。通过设置ADC的分辨率,可以决定数字信号的精度。一般来说,分辨率越高,表示能够更准确地测量模拟信号的变化。

ADC可以将传感器采集到的模拟信号转换为数字信号后,供单片机进行处理和控制。例如,在温度控制系统中,ADC可以将温度传感器采集到的模拟信号转换为数字信号,并根据预设的温度范围进行比较和控制。

单片机可以对模拟信号进行采样并转换为数字信号后,可以对这些数据进行分析和处理。例如,可以计算平均值、最大值、最小值等统计量,或者进行滤波、滑动平均等算法处理,这一点是最常用的。

ADC模块的作用是将模拟信号转换为数字信号,使得单片机能够对其进行处理、分析和控制。它在很多应用领域中起到了关键的作用,如传感器数据采集、信号处理、控制系统等。

CH32V307VCT6单片机ADC 模块包含 2 个 12 位的逐次逼近型的模拟数字转换器,最高 14MHz 的输入时钟。支持 16 个 外部通道和 2 个内部信号源采样源。可完成通道的单次转换、连续转换,通道间自动扫描模式、间断模式、外部触发模式、双重采样等功能。可以通过模拟看门狗功能监测通道电压是否在阈值范围内。

**主要特性:**12位分辨率;支持16个外部通道和2个内部信号源采样;多通道的多种采样转换方式:单次、连续、扫描、触发、间断等;数据对齐模式:左对齐、右对齐;采样时间可按通道分别编程;规则转换和注入转换均支持外部触发;模拟看门狗监测通道电压,自校准功能;双重模式;ADC 通道输入范围:0≤VIN≤VDDA ;输入增益可调,可实现小信号放大采样,详细功能请查看用户手册,寄存器功能建议大家多看几遍哈。下面上ADC功能的主要代码。

**void** **ADC\_Function\_Init**(**void**)//ADC初始化

{

RCC\_APB2PeriphClockCmd(RCC\_APB2Periph\_GPIOA | RCC\_APB2Periph\_ADC1, *ENABLE*); //使能GPIOA时钟和ADC

   RCC\_AHBPeriphClockCmd(RCC\_AHBPeriph\_DMA1, *ENABLE*);      //启用DMA1时钟


   RCC\_ADCCLKConfig(RCC\_PCLK2\_Div6);       //ADC时钟分配配置,6分频(72Mhz/6=12Mhz),ADC时钟频率不能大于14Mhz


   GPIO\_InitTypeDef GPIO\_InitStructure = { 0 };                //定义结构体配置GPIO

   GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_0 | GPIO\_Pin\_1;//设置GPIO口

   GPIO\_InitStructure.GPIO\_Mode = *GPIO\_Mode\_AIN*; //GPIO模式为模拟输入

   GPIO\_Init(GPIOA, &GPIO\_InitStructure);


   ADC\_RegularChannelConfig(ADC1, ADC\_Channel\_0, 1, ADC\_SampleTime\_239Cycles5); //配置ADC规则组,在规则组的序列1写入通道0,采样时间55.5个周期

   ADC\_RegularChannelConfig(ADC1, ADC\_Channel\_1, 2, ADC\_SampleTime\_239Cycles5); //配置ADC规则组,在规则组的序列2写入通道1,采样时间55.5个周期


   ADC\_InitTypeDef ADC\_InitStructure = { 0 };

   ADC\_InitStructure.ADC\_Mode = ADC\_Mode\_Independent;  //配置ADC为独立模式

   ADC\_InitStructure.ADC\_ScanConvMode = *ENABLE*;        //多通道模式下开启扫描模式

   ADC\_InitStructure.ADC\_ContinuousConvMode = *ENABLE*;  //设置开启连续转换模式

   ADC\_InitStructure.ADC\_ExternalTrigConv = ADC\_ExternalTrigConv\_None; //设置转换不是由外部触发启动,软件触发启动

   ADC\_InitStructure.ADC\_DataAlign = ADC\_DataAlign\_Right; //设置ADC数据右对齐

   ADC\_InitStructure.ADC\_NbrOfChannel = 2;                //规则转换的ADC通道的数目

   ADC\_Init(ADC1, &ADC\_InitStructure);


   ADC\_Cmd(ADC1, *ENABLE*);      //使能ADC1


   ADC\_ResetCalibration(ADC1); //重置ADC1校准寄存器。


   **while** (ADC\_GetResetCalibrationStatus(ADC1))

       ; //等待复位校准结束


   ADC\_StartCalibration(ADC1); //开启AD校准


   **while** (ADC\_GetCalibrationStatus(ADC1))

       ;      //等待校准结束
}

上面代码是ADC的初始化代码,想要写多通道ADC,需要更改的代码是:

GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_0|GPIO\_Pin\_1;//将GPIOA的第0引脚设置为要使用的引脚

这句代码是初始化ADC要使用的引脚,ADC功能对应的GPIO可以查看芯片手册,这里只用了两个通道,所以只需设置两个GPIO。芯片手册已上传。

ADC\_InitStructure.ADC\_NbrOfChannel = 2;//顺序进行规则转换的ADC通道的数目(根据ADC采集通道数量修改)

这句代码根据你设置的通道数来修改,这里只用两通道。其他的代码可以根据自己的需求对着用户手册来修改。用户手册已上传。

DMA(直接内存访问)是在嵌入式系统中常见的功能模块,使用DMA可以减轻CPU的负担,将数据传输任务交给DMA处理,从而释放CPU的资源,使其能够更有效地执行其他任务。提高系统的整体性能。

ADC将模拟信号转换为数字值。通过使用DMA,可以实现连续的、高速的数据采集,并将转换后的数据直接传输到内存中,而无需CPU的干预。这对于需要实时采集大量数据的应用非常有用,DMA可以在不占用CPU时间的情况下进行数据传输,降低系统的功耗。通过将ADC的转换结果直接传输到内存中,方便进行后续的数据处理和分析。CPU读取内存中的数据进行各种算法、滤波、计算等操作,以满足特定的应用需求。ADC+DMA的组合可以提供高性能、实时数据采集和处理、节省功耗等优势,适用于需要快速、连续采集大量数据的应用场景。

CH32V307VCT6单片机的主要特点:多个独立可配置通道;每个通道都直接连接专用的硬件DMA请求,并支持软件触发;支持循环的缓冲器管理;多个通道之间的请求优先权可以通过软件编程设置(最高、高、中和低),优先权设置相等时由通道号决定(通道号越低优先级越高);支持外设到存储器、存储器到外设、存储器到存储器之间的传输; 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标;可编程的数据传输字节数目最大为65535。详细的功能请查看用户手册。用户手册很重要的,下面上DMA的主要代码。

//DMA初始化设置

**void** **DMA\_Tx\_Init**(**void**)

{

    RCC\_AHBPeriphClockCmd( RCC\_AHBPeriph\_DMA1, *ENABLE* );//DMA时钟使能

    DMA\_DeInit(DMA1\_Channel1); //复位DMA控制器

    DMA\_InitTypeDef DMA\_InitStructure;                      //定义结构体配置DMA

    DMA\_InitStructure.DMA\_PeripheralBaseAddr = (u32) &ADC1->RDATAR; //配置外设地址为ADC数据寄存器地址

    DMA\_InitStructure.DMA\_MemoryBaseAddr = (u32) TxBuf;          //配置存储器地址为读取ADC值地址

    DMA\_InitStructure.DMA\_DIR = DMA\_DIR\_PeripheralSRC;              //配置数据源为外设,即DMA传输方式为外设到存储器

    DMA\_InitStructure.DMA\_BufferSize = 2;                           //设置DMA数据缓冲区大小

    DMA\_InitStructure.DMA\_PeripheralInc = DMA\_PeripheralInc\_Disable;                           //设置DMA外设递增模式关闭

    DMA\_InitStructure.DMA\_MemoryInc = DMA\_MemoryInc\_Enable;         //设置DMA存储器递增模式开启

    DMA\_InitStructure.DMA\_PeripheralDataSize = DMA\_PeripheralDataSize\_HalfWord; //设置外设数据大小为半字,即两个字节

    DMA\_InitStructure.DMA\_MemoryDataSize = DMA\_MemoryDataSize\_HalfWord;         //设置存储器数据大小为半字,即两个字节

    DMA\_InitStructure.DMA\_Mode = DMA\_Mode\_Circular;     //设置DMA模式为循环传输模式

    DMA\_InitStructure.DMA\_Priority = DMA\_Priority\_High; //设置DMA传输通道优先级为高,当使用一 DMA通道时,优先级设置不影响

    DMA\_InitStructure.DMA\_M2M = DMA\_M2M\_Disable;        //因为此DMA传输方式为外设到存储器,因此禁用存储器到存储器传输方式

    DMA\_Init(DMA1\_Channel1, &DMA\_InitStructure);        //初始化DMA1的通道1,ADC1的硬件触发接在DMA1的通道1上,所以必须使用DMA1通道1


    DMA\_Cmd(DMA1\_Channel1, *ENABLE*); //启动DMA1通道1

    ADC\_DMACmd(ADC1, *ENABLE*);       // 使能ADC DMA 请求

    ADC\_SoftwareStartConvCmd(ADC1, *ENABLE*);       // 由于没有采用外部触发,所以使用
}

以上代码就是DMA初始化设置,其他的代码可以根据自己的需求对着用户手册来修改。

DMA\_InitStructure.DMA\_BufferSize = 2;                           //设置DMA数据缓冲区大小

这句代码可以更改ADC的采集通道。

IIC(Inter-Integrated Circuit)是一种串行通信协议,IIC可以实现两个或多个设备之间的数据传输。支持同时传输多个设备,并且可以通过地址选择器选择与哪个设备进行通信。IIC只需要两根线(SDA和SCL)来连接设备,相比于其他串行通信协议(如SPI),线路连接更为简单。IIC允许多个主机共享同一个总线,并且可以在总线上进行读写操作,实现多主机之间的通信。IIC协议使用了双向通信,设备在不使用总线时可以进入睡眠状态,从而降低功耗。IIC是一种常用的串行通信协议,广泛应用于各种电子设备中,可以实现设备之间的数据传输和设备控制。

CH32V307VCT6单片机内部集成电路总线(I2C)广泛用在微控制器和传感器及其他片外模块的通讯上,它本身支持多主多从模式,仅仅使用两根线(SDA 和 SCL)就能以100KHz(标准和400KHz(快速)两种速度通讯。I2C总线还兼容 SMBus协议,不仅支持I2C的时序,还支持仲裁、定时和DMA,拥有CRC校验功能。

主要特征:支持主模式和从模式;支持7位或10位地址;从设备支持双7位地址;支持两种速度模式:100KHz和400KHz;多种状态模式,多种错误标志;支持加长的时钟功能;2 个中断向量;支持DMA;支持PEC;兼容SMBus。详细的功能看看用户手册哈,时序图和寄存器这些用户手册芯片手册写得很清楚的。下面是IIC的主要代码。

**void** **IIC\_Init**(u32 bound, u16 address)//初始化IIC设置

{

GPIO\_InitTypeDef GPIO\_InitStructure = {0};//定义结构体变量GPIO\_InitStructure,并将它初始化为0

I2C\_InitTypeDef  I2C\_InitTSturcture = {0};//定义结构体变量I2C\_InitTSturcture,并将它初始化为0


RCC\_APB2PeriphClockCmd(RCC\_APB2Periph\_GPIOB | RCC\_APB2Periph\_AFIO, *ENABLE*);//使能GPIOB和AFIO的时钟

GPIO\_PinRemapConfig(GPIO\_Remap\_I2C1, *ENABLE*);//将I2C1的引脚映射到其他的GPIO端口

RCC\_APB1PeriphClockCmd(RCC\_APB1Periph\_I2C1, *ENABLE*);//使能I2C1的时钟


GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_8;  //设置SCK引脚(GPIO\_Pin\_8)

GPIO\_InitStructure.GPIO\_Mode = *GPIO\_Mode\_AF\_OD*;//工作模式为复用开漏输出

GPIO\_InitStructure.GPIO\_Speed = *GPIO\_Speed\_50MHz*;//速度为50MHz

GPIO\_Init(GPIOB, &GPIO\_InitStructure);//将GPIO\_InitStructure结构体中的配置应用到GPIOB引脚上


GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_9;   //设置SDA引脚(GPIO\_Pin\_9)

GPIO\_InitStructure.GPIO\_Mode = *GPIO\_Mode\_AF\_OD*;//工作模式为复用开漏输出

GPIO\_InitStructure.GPIO\_Speed = *GPIO\_Speed\_50MHz*;//速度为50MHz

GPIO\_Init(GPIOB, &GPIO\_InitStructure);//将GPIO\_InitStructure结构体中的配置应用到GPIOB引脚上


I2C\_InitTSturcture.I2C\_ClockSpeed = bound;//设置I2C的时钟频率为bound

I2C\_InitTSturcture.I2C\_Mode = I2C\_Mode\_I2C;//工作模式为标准模式(I2C\_Mode\_I2C)

I2C\_InitTSturcture.I2C\_DutyCycle = I2C\_DutyCycle\_16\_9;//设置占空比为16:9(I2C\_DutyCycle\_16\_9)

I2C\_InitTSturcture.I2C\_OwnAddress1 = address;//设置地址为address

I2C\_InitTSturcture.I2C\_Ack = I2C\_Ack\_Enable;//使能应答功能(I2C\_Ack\_Enable)

I2C\_InitTSturcture.I2C\_AcknowledgedAddress = I2C\_AcknowledgedAddress\_7bit;//设置应答地址长度为7位(I2C\_AcknowledgedAddress\_7bit)

I2C\_Init(I2C1, &I2C\_InitTSturcture);//将I2C\_InitTSturcture结构体中的配置应用到I2C1外设上


I2C\_Cmd(I2C1, *ENABLE*);//使能I2C1外设
**#if**(I2C\_MODE == HOST\_MODE)//如果I2C\_MODE被定义为HOST\_MODE,则通过I2C\_AcknowledgeConfig函数使能I2C1的应答功能

I2C\_AcknowledgeConfig(I2C1, *ENABLE*);
**#endif**

}

以上是IIC初始化代码,可以根据自己的需要看着芯片手册来更改这里。

写完了IC初始化,因为打算用OLED显示屏显示电压的数值,还需要写OLED显示屏的底层驱动代码。下面是OLED显示屏的底层驱动代码。

**void** **ssd1306\_write\_cmd**(uint8\_t data)//发送命令函数

{

**while**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_BUSY) != *RESET*)//while循环检查I2C1总线是否繁忙,即不为0

        ;


    I2C\_GenerateSTART(I2C1, *ENABLE*);//开始传输数据


    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_MODE\_SELECT))//检查I2C1是否成功进入主模式

        ;

    I2C\_Send7bitAddress(I2C1, SSD1306\_ADDRESS, I2C\_Direction\_Transmitter);//发送目标设备地址(SSD1306\_ADDRESS)给I2C1总线,发送的数据是发送模式


    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_TRANSMITTER\_MODE\_SELECTED))//检查I2C1是否成功进入主模式的发送模式

        ;

    **if**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_TXE) != *RESET*)//如果TXE标志位为RESET

            {

                I2C\_SendData(I2C1, 0x00);//向I2C1总线发送一个字节的数据(0x00)

            }

    **if**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_TXE) != *RESET*)//判断I2C1是否准备好发送数据

                    {

                        I2C\_SendData(I2C1, data);//向I2C1总线发送需要发送的数据

                    }

    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_BYTE\_TRANSMITTED))//检查I2C1是否成功发送完毕一个字节的数据

            ;

    I2C\_GenerateSTOP(I2C1, *ENABLE*);//结束数据传输
}

**void** **ssd1306\_write\_date**(uint8\_t data)//写入数据

{

**while**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_BUSY) != *RESET*)//等待I2C总线不为0

        ;


    I2C\_GenerateSTART(I2C1, *ENABLE*);//启动传输


    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_MODE\_SELECT))//等待主模式选择事件发生

        ;

    I2C\_Send7bitAddress(I2C1, SSD1306\_ADDRESS, I2C\_Direction\_Transmitter);//发送从设备地址,指定传输方向为发送


    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_TRANSMITTER\_MODE\_SELECTED))//等待主模式发送器模式选择事件发生

        ;

    **if**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_TXE) != *RESET*)//判断是否可以发送数据

            {

                I2C\_SendData(I2C1, 0x40);//发送数据

            }

    **if**(I2C\_GetFlagStatus(I2C1, I2C\_FLAG\_TXE) != *RESET*)//再次判断是否可以发送数据

                    {

                        I2C\_SendData(I2C1, data);// 发送实际数据

                    }

    **while**(!I2C\_CheckEvent(I2C1, I2C\_EVENT\_MASTER\_BYTE\_TRANSMITTED))//等待字节传输完成

            ;

    I2C\_GenerateSTOP(I2C1, *ENABLE*);//结束传输
}

//坐标设置:也就是在哪里显示

**void** **oled\_set\_pos**(uint8\_t x, uint8\_t y)

{

//以下3个寄存器只在页录址的模式下有效

ssd1306\_write\_cmd(0xb0 + y);               //页地址设置 0xb0-0xb7

ssd1306\_write\_cmd(((x&0xf0)>>4)|0x10);   //列高地址设置

ssd1306\_write\_cmd((x&0x0f)|0x01);             //列低位地址设置

}

**void** **OLED\_Fill**(**unsigned** **char** fill\_Data)//全屏填充

{

**unsigned** **char** m,n;

**for**(m=0;m<8;m++)

{

    ssd1306\_write\_cmd(0xb0+m);      //page0-page1

    ssd1306\_write\_cmd(0x00);        //low column start address 列地址的低4位

    ssd1306\_write\_cmd(0x10);        //high column start address 列地址的高4位

    **for**(n=0;n<128;n++)

        {

            ssd1306\_write\_date(fill\_Data);

        }

}
}

//开启oled显示

**void** **oled\_display\_on**(**void**)

{

ssd1306\_write\_cmd(0x8D);   //SET DCDC命令

ssd1306\_write\_cmd(0x14);   //DCDC ON

ssd1306\_write\_cmd(0xAF);   // DISPLAY ON

}

//关闭oled显示

**void** **oled\_display\_off**(**void**)

{

ssd1306\_write\_cmd(0x8D);  //SET DCDC命令

ssd1306\_write\_cmd(0x10);  //DCDC OFF

ssd1306\_write\_cmd(0xAE);  //DISPLAY OFF

}

//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样

**void** **oled\_clear**(**void**)

{

uint8\_t i, n;

**for**(i=0;i<8;i++)

{

ssd1306\_write\_cmd(0xb0+i);   //设置页地址(0-7)

ssd1306\_write\_cmd(0x00);     //设置显示位置-列低地址

ssd1306\_write\_cmd(0x10);     //设置显示位置-列高地址

**for**(n=0;n<128;n++)

{

  ssd1306\_write\_date(0);     //更新显示

}
}

}

//清楚某一行

**void** **oled\_clear\_line**(uint8\_t line, uint8\_t num)

{

uint8\_t i,n;

**for**(i=line;i<line+num;i++)

{

    ssd1306\_write\_cmd(0xb0+i);   //设置页地址(0-7)

    ssd1306\_write\_cmd(0x00);     //设置显示位置-列低地址

    ssd1306\_write\_cmd(0x10);     //设置显示位置-列高地址

    **for**(n=0;n<128;n++)

    {

      ssd1306\_write\_date(0);     //更新显示

    }

}
}

//在指定位置显示一个字符,包括部分字符

//x:0-127, y:0-7

//Char\_size:选择字体16/12

**void** **oled\_show\_char**(uint8\_t x, uint8\_t y, uint8\_t chr, uint16\_t char\_size)

{

uint8\_t c=0, i=0;

c = chr-32; //得到偏移后的值

**if**(x>MAX\_COLUMN-1)

{

x=0;

y++;
}

**if**(char\_size == 16)

{

oled\_set\_pos(x,y);

**for**(i=0;i<8;i++)

{

  ssd1306\_write\_date(asc2\_1608[c]);//先写上半部分

}

oled\_set\_pos(x,y+1);

**for**(i=0;i<8;i++)

{

  ssd1306\_write\_date(asc2\_1608[c][i+8]);//后写下半部分

}
}

}

//显示一个字符串

**void** **oled\_show\_string**(uint8\_t x, uint8\_t y, **char** \*str, uint8\_t char\_size)

{

**unsigned** **char** j=0;

**while**(str[j]!='\\0')

{

oled\_show\_char(x,y,str[j],char\_size);

x+=8;

**if**(x>120)

{

  x=0;

  y+=2;

}

j++;//移动一次就是一个page,取值0-7
}

}

//--------------------------------------------------------------

/\*

Prototype      : void OLED\_ShowChar(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)

Calls          :

Parameters     : x,y -- 起始点坐标(x:0\~127, y:0\~7);

ch[] -- 要显示的字符串;

TextSize -- 字符大小(1:6\*8 ; 2:8\*16)

Description    : 显示codetab.h中的ASCII字符,有6\*8和8\*16可选择

//--------------------------------------------------------------

\*/

**void** **OLED\_ShowStr**(**unsigned** **char** x, **unsigned** **char** y, **unsigned** **char** ch[], **unsigned** **char** TextSize)

{

**unsigned** **char** c = 0,i = 0,j = 0;

**switch**(TextSize)

{

    **case** 6:

    {

        **while**(ch[j] != '\\0')

        {

            c = ch[j] - 32;

            **if**(x > 126)

            {

                x = 0;

                y++;

            }

            oled\_set\_pos(x,y);

            **for**(i=0;i<6;i++)

                ssd1306\_write\_date(asc2\_0806[c]);

    x += 6;

            j++;

        }

    }**break**;

**case** 12:

    {

        **while**(ch[j] != '\\0')

        {

            c = ch[j] - 32;

            **if**(x > 126)

            {

                x = 0;

                y++;

            }

            oled\_set\_pos(x,y);

            **for**(i=0;i<6;i++)

                ssd1306\_write\_date(asc2\_1206[c]);

    oled\_set\_pos(x,y+1);

            **for**(i=0;i<6;i++)

                ssd1306\_write\_date(asc2\_1206[c][i+6]);

    x += 6;

            j++;


        }

    }**break**;

    **case** 16:

    {

        **while**(ch[j] != '\\0')

        {

            c = ch[j] - 32;

            **if**(x > 120)

            {

                x = 0;

                y++;

            }

            oled\_set\_pos(x,y);

            **for**(i=0;i<8;i++)

                ssd1306\_write\_date(asc2\_1608[c]);//先写上半部分

            oled\_set\_pos(x,y+1);

            **for**(i=0;i<8;i++)

                ssd1306\_write\_date(asc2\_1608[c][i+8]);//后写下半部分

            x += 8;

            j++;

        }

    }**break**;

**case** 24:

    {

        **while**(ch[j] != '\\0')

        {

            c = ch[j] - 32;

            **if**(x > 120)

            {

                x = 0;

                y++;

            }

            oled\_set\_pos(x,y);

            **for**(i=0;i<12;i++)

                ssd1306\_write\_date(asc2\_2412[c]);//先写上半部分

            oled\_set\_pos(x,y+1);

            **for**(i=0;i<12;i++)

                ssd1306\_write\_date(asc2\_2412[c][i+12]);//后写下半部分

    oled\_set\_pos(x,y+2);

            **for**(i=0;i<12;i++)

                ssd1306\_write\_date(asc2\_2412[c][i+24]);//后写下半部分

            x += 12;

            j++;

        }

    }**break**;

}
}

/\*\*

\* @brief  OLED次方函数

\* @retval 返回值等于X的Y次方

\*/

uint32\_t **OLED\_Pow**(uint32\_t X, uint32\_t Y)

{

uint32\_t Result = 1;

**while** (Y--)

{

    Result \*= X;

}

**return** Result;
}

/\*\*

\* @brief  OLED显示数字(十进制,正数)

\* @param  Line 起始行位置,范围:1\~4

\* @param  Column 起始列位置,范围:1\~16

\* @param  Number 要显示的数字,范围:0\~4294967295

\* @param  Length 要显示数字的长度,范围:1\~10

\* @retval 无

\*/

**void** **OLED\_ShowNum**(uint8\_t Line, uint8\_t Column, uint32\_t Number, uint8\_t Length)

{

uint8\_t i;

**for** (i = 0; i < Length; i++)

{

    oled\_show\_char(Line, Column + i, Number / OLED\_Pow(10, Length - i - 1) % 10 + '0',16);

}
}

//OLED初始化

**void** **oled\_init**(**void**)

{

IIC\_Init(400000,0x02);//初始化I2C总线,设置传输速率为400kHz

Delay\_Ms(600);

//ssd1306复位之后,默认是页寻址方式

ssd1306\_write\_cmd(0xAE);  //发送命令字节0xAE,关闭显示

ssd1306\_write\_cmd(0x00);  //发送命令字节0x00,设置列低地址

ssd1306\_write\_cmd(0x10);  //发送命令字节0x10,设置列高地址

ssd1306\_write\_cmd(0x40);  //发送命令字节0x40,设置起始行地址

ssd1306\_write\_cmd(0xB0);  //发送命令字节0xB0,设置页地址

ssd1306\_write\_cmd(0x81);  //发送命令字节0x81,设置对比度控制

ssd1306\_write\_cmd(0xFF);  //--128

ssd1306\_write\_cmd(0xA1);  // 发送命令字节0xA1,设置段重映射为0到127

ssd1306\_write\_cmd(0xA6);  // 发送命令字节0xA6,设置显示正常模式

ssd1306\_write\_cmd(0xA8);  //发送命令字节0xA8,设置复用率为1到64

ssd1306\_write\_cmd(0x3F);  // 发送命令字节0x3F,设置Duty为1/32

ssd1306\_write\_cmd(0xC8);  // 发送命令字节0xC8,设置扫描方向

ssd1306\_write\_cmd(0xD3);  //发送命令字节0xD3,设置显示偏移量

ssd1306\_write\_cmd(0x00);  //发送命令字节0x00,设置偏移量为0

ssd1306\_write\_cmd(0xD5);  //发送命令字节0xD5,设置显示时钟分频比和振荡器频率

ssd1306\_write\_cmd(0x80);//发送命令字节0x80,设置显示时钟分频比为默认值

ssd1306\_write\_cmd(0xD8); //发送命令字节0xD8,设置区域颜色模式关闭

ssd1306\_write\_cmd(0x05); //发送命令字节0x05,设置区域颜色模式关闭

ssd1306\_write\_cmd(0xD9);  //发送命令字节0xD9,设置预充电周期

ssd1306\_write\_cmd(0xF1);  //发送命令字节0xF1,设置预充电周期为默认值

ssd1306\_write\_cmd(0xDA);   //发送命令字节0xDA,设置COM引脚硬件配置

ssd1306\_write\_cmd(0x12);//发送命令字节0x12,设置COM引脚硬件配置

ssd1306\_write\_cmd(0xDB); //发送命令字节0xDB,设置Vcomh

ssd1306\_write\_cmd(0x30); //发送命令字节0x30,设置Vcomh为0.77倍的Vcc

ssd1306\_write\_cmd(0x8D); //发送命令字节0x8D,启用电荷泵

ssd1306\_write\_cmd(0x14); //发送命令字节0x14,启用电荷泵

ssd1306\_write\_cmd(0xAF); //发送命令字节0xAF,打开OLED面板显示

}

以上代码就是OLED底层驱动代码,下面代码是主函数代码。

**void** **shucudianya**(**void**) //电压显示

{

    aa=TxBuf[0]/1.24;

    OLED\_ShowNum(1,4,aa/10000,1);//

    OLED\_ShowNum(8,4,aa%10000/1000,1);//

    oled\_show\_char(16,4,'.',16);//

    OLED\_ShowNum(24,4,aa%1000/100,1);//

    OLED\_ShowNum(32,4,aa%100/10,1);//

    OLED\_ShowNum(40,4,aa%10,1);//

    oled\_show\_char(48,4,'V',16);//


    bb=TxBuf[1]/1.24;

    OLED\_ShowNum(64,4,bb/10000,1);//

    OLED\_ShowNum(72,4,bb%10000/1000,1);//

    oled\_show\_char(80,4,'.',16);//

    OLED\_ShowNum(88,4,bb%1000/100,1);//

    OLED\_ShowNum(96,4,bb%100/10,1);//

    OLED\_ShowNum(102,4,bb%10,1);//

    oled\_show\_char(110,4,'V',16);//
}

**int** **main**(**void**)

{

SystemCoreClockUpdate();//启动时钟

Delay\_Init();//时间函数初始化

USART\_Printf\_Init(115200);//设置波特率为115200


ADC\_Function\_Init();//ADC初始化

DMA\_Tx\_Init();//DMA初始化设置

oled\_init();//OLED初始化

oled\_clear();//清屏

OLED\_ShowStr(0,0,"CH32V307!",16);

OLED\_ShowStr(0,2,"ELECFANS",16);

**while**(1)

{

    shucudianya();//采集电压显示

    Delay\_Ms(30);

    **printf**("AD1:%d,AD2:%d\\r\\n",TxBuf[0],TxBuf[1]);//打印ADC通道的电压值

    Delay\_Ms(30);

}
}

下面是实验效果图

![](file:///C:/usertemp/AppData/Local/Temp/msohtmlclip1/01/clip_image002.png)

                       串口打印出来的电压值
![](file:///C:/usertemp/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg)

                          ADC通道一采集的电压值
![](file:///C:/usertemp/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg)

                            ADC通道0采集VCC分压后的电压值
上面演示了ADC+DMA采集电压,通过串口打印出电压值和OLED显示屏显示电压值,用万用表测出来的电压值和显示的电压差不多,电压采集正常。
微信图片_20230903194924.png
微信图片_20230903195619.jpg
微信图片_20230903195636.jpg

CH32FV2x_V3xRM用户手册.PDF

6.79 MB, 下载次数: 0

CH32V307DS0.PDF

1.37 MB, 下载次数: 0

ADC_DMA.rar

500.75 KB, 下载次数: 0

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|国产电子社区 ( 沪ICP备2023018578号-1|

苏公网安备 32011102010465号


)|网站地图

GMT+8, 2024-7-27 18:32 , Processed in 0.068105 second(s), 23 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表