|
发表于 2023-9-5 15:28:46
|
查看: 1192 |
回复: 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显示屏显示电压值,用万用表测出来的电压值和显示的电压差不多,电压采集正常。
|
|