硬件介绍:2023寒假一起练平台(1)基于STEP Pico 的嵌入式系统学习平台。是一个硬禾设计制造的树莓派Pico。分为两个部分,硬禾版本树莓派Pico核心模块,较树莓派Pico,修改了micro USB为type-C口,增加了一个run按键,添加了4个WS2812B的LED。扩展板上集成了12个WS2812B RGB三色灯;1个姿态传感器;1个128*64 OLED显示屏;1个蜂鸣器;1个可调电位计(用于电压表);1路音频信号输入(用于示波器);8位R-2R电阻网络构成的DAC(用于DDS信号发生器)。
任务选择:这次我选择的项目4 - 制作一个电压表。具体要求:利用板上的电位计调节电压从0-3.3V之间变化,在OLED显示屏上显示电压值,可以以数字的方式,也可以以图形的方式来显示。实现方式:调节电位计产生0-3.3V之间变化的电压,树莓派Pico内部的ADC对该电压进行采集,得到0-4095之间的数值,经过计算以后对应到相应的电压值,再通过OLED显示屏显示出来。
这块板块支持MicroPython、Arduino、C/C++。电子森林提供了非常详细的MicroPython的教程。这次想尝试一下使用Arduino来实现任务。开发工具选择vscode+platformio。整体流程很简单,如下图。
任务实现:要实现任务,首先是数据来源。即获得电位器的电压值。查看电路图可知电位器接在GPIO28脚,电压范围0~3.3v。直接调用Arduino中的ADC函数analogRead(),即可获得该通道的ADC值,这里的值并不是直接返回电位器上的电压值,而是处理过的数值,其数值范围为0-1023。这里有一点点疑惑,使用Mpy读取ADC值是0-65535;那么RP2040的ADC的分辨率到底是多少位的呢?查了官方的文档:明确了rp2040的ADC位500ksps,12-bit分辨率。这样Arduino的函数实际是降低了ADC的分辨率,不知道应该如何设置能够获得到12位分辨率的值。这里姑且先使用10位分辨率的ADC值。将获得的ADC映射到0~3.3v电压上。
float readVoltage(uint16_t *adcval)
{
// 读取ADC的值 范围 0~1024
uint16_t val = analogRead(ADCPIN);
*adcval = val;
return (float)val * 3.3 / 1024.0;
}
拿到了电压值,接下来就是展示了。硬件上板子提供了一块128x64的oled显示屏。使用的是SPI接口。这里使用了Arduino的U8g2库。如果手工驱动屏幕,那需要对要显示的内容取模,然后逐一显示,还是比较麻烦的。而U8g2库则可以很轻松地支持起这个Oled显示屏,普通的字符显示变得很容易了。
这里主要需要显示的内容为当前的电压值。我选用24像素的字体,来显示电压值。额外地将ADC读取的原始值、当前电压索占百分比也同时显示出来,就是用小一点的字体(16像素)来显示。
成功显示了电位器的电压值,总觉得有显示有些单调。于是看着Oled四周有一圈WS2812B,一共12颗。WS2812B是一个单总线工作的全彩LED灯,在各种灯带中常见这个LED。能够做成很绚丽的光电效果。这里使用WS2812FX的库,来驱动WS2812B。控制着12个LED灯实现单个灯旋转闪烁的效果。颜色使用电压对应的伪彩色。即将电位器的电压0~3.3V映射到RGB的全色域。表现为从蓝色到紫色。这样就可以直观地用颜色表示当前电压了。
// 浮点数转颜色 伪彩色
float MinVol=0.0 , MaxVol=3.3, a, b, c, d;
void Getabcd() {
a = MinVol + (MaxVol - MinVol) * 0.2121;
b = MinVol + (MaxVol - MinVol) * 0.3182;
c = MinVol + (MaxVol - MinVol) * 0.4242;
d = MinVol + (MaxVol - MinVol) * 0.8182;
}
uint32_t GetColor(float val) {
byte red = 0, green = 0, blue = 0;
red = constrain(255.0 / (c - b) * val - ((b * 255.0) / (c - b)), 0, 255);
if ((val > MinVol) & (val < a)) {
green = constrain(255.0 / (a - MinVol) * val - (255.0 * MinVol) / (a - MinVol), 0, 255);
} else if ((val >= a) & (val <= c)) {
green = 255;
} else if (val > c) {
green = constrain(255.0 / (c - d) * val - (d * 255.0) / (c - d), 0, 255);
} else if ((val > d) | (val < a)) {
green = 0;
}
if (val <= b) {
blue = constrain(255.0 / (a - b) * val - (255.0 * b) / (a - b), 0, 255);
} else if ((val > b) & (val <= d)) {
blue = 0;
} else if (val > d) {
blue = constrain(240.0 / (MaxVol - d) * val - (d * 240.0) / (MaxVol - d), 0, 240);
}
程序主体部分就很简单了,不停地循环查询电位器的电压值,然后显示,再去控制LED灯,循环此过程。
void setup()
{
Serial.begin(115200);
u8g2.begin(); // 选择U8G2模式,或者U8X8模式
ws2812fx.init();
ws2812fx.setBrightness(100);
ws2812fx.setSpeed(1000);
ws2812fx.setColor(GREEN);
// ws2812fx.setMode(FX_MODE_STATIC);
ws2812fx.setMode(13);
ws2812fx.start();
}
void loop()
{
uint16_t adcval;
char str[60];
ws2812fx.service();
float voltage = readVoltage(&adcval);
Serial.print(adcval);
Serial.print(" ");
Serial.print(voltage);
Serial.print(" ");
Serial.println();
u8g2.clearBuffer(); // 清除内部缓冲区
u8g2.setFont(u8g2_font_ncenB24_tr);
sprintf(str, "%1.1f%s", voltage, "V");
u8g2.drawStr(35, 30, str); // write something to the internal memory
u8g2.setFont(u8g2_font_helvR12_tr);
sprintf(str, "%4d %3d%s", adcval, int(adcval / 10.23), "%");
u8g2.drawStr(20, 60, str);
u8g2.sendBuffer(); // transfer internal memory to the display
delay(100);
Getabcd();
ws2812fx.setColor(GetColor(voltage));
}
心得体会:非常感谢电子森林举办的寒假一起练活动;感谢硬禾学堂提供的课程。课程很精彩、板子很好玩!
,