前言
LED点阵模块也是显示屏,显示屏就不只是用来显示文字,还应该可以用来画图。
那么一个可以绘图的显示屏应该具有哪些条件:
1)要有一块区域做为显存,就是要显示的数据,在单片机中为了节约空间要用位来控制led亮灭。一个字节8位,可以控制8个led。16x64LED就需要16x64/8=128个字节的显存,在单片机中用数组表示。
2)显存有了,就需要绘图函数来控制数组的值。这部分涉及的数学方面的知识,还有对位的控制,比较烧脑,超出个人能力。所以我问了AI,它给了我一段程序,见后文。
3)下一步,就回归正题,在单片机显示。没有新花样,还是行扫描,595。
一、像画布一样操作点阵屏源码
1.main.c
#include <STC89C5xRC.H>
#include <intrins.h>
#include <absacc.h>
#include <string.h>
#include "draw.h"
sbit SH_CP = P1^5;
sbit DS = P2^7;
sbit ST_CP = P1^6;
typedef unsigned int Uint16;
typedef unsigned char Uint8;
extern unsigned char xdata canvas[WIDTH * HEIGHT / 8];
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 122;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void delayms(unsigned int m)
{
int i,j;
for(i=0; i<m; i++)
for(j=0; j<120; j++);
}
//595就是串行输入,凑足8个并行输出,SH_CP上升沿管串行输入
void HC595(unsigned char dat)
{
unsigned char j;
for(j=0;j<8;j++)
{
SH_CP = 0; //为移位准备
DS = dat & 0x01; //先低位
dat=dat>>1;
//DS=dat&0x80;
//dat=dat<<1;
SH_CP =1; //上升沿,移位
}
}
void drawbuff()
{
unsigned char i,j,n,index;
for(n=0;n<24;n++) //为了字不闪
{
for(j=0;j<16;j++)
{
for(i=0;i<8;i++)
{
index=i+j*8;
HC595(~canvas[index]);
}
ST_CP = 0;
ST_CP = 1;
P1=15-j;
Delay1000ms();
P1=0xff;
}
}
}
void testLed()
{
unsigned char k,i,j,dat;
for(k=0;k<16;k++)
{
for(i=0;i<4;i++)
{
HC595(0x00);
HC595(0x00);
}
ST_CP = 0;
ST_CP = 1;
P1=k;
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
P1=0xff;
}
}
void main(){
clearScreen();
testLed();
draw_rectangle(0, 0,10, 20, 1);
draw_rectangle(2, 25,10, 31, 0);
draw_line(10, 20, 15, 30);
draw_point(10,48);
draw_circle(8, 36 , 8, 0);
while(1){
drawbuff();
}
}
2.绘图实现文件draw.c
下面程序是AI生成的,在windows环境下运行基本正常。移植到51上,改了一些类型定义,有部分函数不太正常了。比如画直线的draw_line函数。
另外需要注意的是,所有函数中x为纵坐标,y为横坐标。
#include <stdio.h>
#include <math.h>
#include "draw.h"
unsigned char xdata canvas[WIDTH * HEIGHT / 8]; // 画布,使用一个字节的位来表示一个点是否被画
// 画点
void draw_point(unsigned int x, unsigned int y) {
unsigned int index = x * HEIGHT + y; // 计算该点在 canvas 中的下标
canvas[index / 8] |= 1 << (index % 8); // 将该点的位置置为 1
}
// 擦除点
void erase_point(unsigned int x, unsigned int y) {
unsigned int index = x * HEIGHT + y; // 计算该点在 canvas 中的下标
canvas[index / 8] &= ~(1 << (index % 8)); // 将该点的位置置为 0
}
// 判断点是否被画
unsigned int is_point_drawn(unsigned int x, unsigned int y) {
unsigned int index = x * HEIGHT + y; // 计算该点在 canvas 中的下标
return canvas[index / 8] & (1 << (index % 8)); // 判断该点的位置是否为 1
}
// 画线
void draw_line(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) {
unsigned int dx = x2 - x1; // 计算 x 轴方向的增量
unsigned int dy = y2 - y1; // 计算 y 轴方向的增量
unsigned int incr = 1; // 确定增量的方向
unsigned int x,y,d;
if (dx < 0) { // 如果 x 轴增量为负数,则将增量方向翻转
dx = -dx;
dy = -dy;
incr = -1;
}
x = x1, y = y1; // 初始点的坐标
d = 0; // 决策变量
draw_point(x, y); // 先画初始点
while (x != x2) { // 沿着 x 轴方向依次画点
x += incr;
d += dy;
if (d >= dx) { // 如果决策变量超过了 dx,则需要向 y 轴方向移动一格
y++;
d -= dx;
} else if (d <= -dx) { // 如果决策变量小于了 -dx,则需要向 y 轴方向移动一格
y--;
d += dx;
}
draw_point(x, y); // 画当前点
}
}
// 画矩形
void draw_rectangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int fill) {
unsigned int x, y;
if (x1 > x2) { // 如果 x1 大于 x2,则交换两个点的坐标
int temp = x1;
x1 = x2;
x2 = temp;
}
if (y1 > y2) { // 如果 y1 大于 y2,则交换两个点的坐标
unsigned int temp = y1;
y1 = y2;
y2 = temp;
}
// 画上下两条边
for (x = x1; x <= x2; x++) {
draw_point(x, y1);
draw_point(x, y2);
}
// 画左右两条边
for (y = y1; y <= y2; y++) {
draw_point(x1, y);
draw_point(x2, y);
}
if (fill) { // 如果需要填充,则将矩形内部的点全部画上
for (x = x1 + 1; x < x2; x++) {
for (y = y1 + 1; y < y2; y++) {
draw_point(x, y);
}
}
}
}
// 画圆
void draw_circle(unsigned int x, unsigned int y, unsigned int r, unsigned int fill) {
unsigned int dx = 0, dy = r;
unsigned int d = 1 - r;
unsigned i;
draw_point(x + dx, y + dy);
draw_point(x + dx, y - dy);
draw_point(x + dy, y + dx);
draw_point(x + dy, y - dx);
while (dx < dy) {
dx++;
if (d < 0) {
d = d + 2 * dx + 1;
} else {
dy--;
d = d + 2 * (dx - dy) + 1;
}
if (fill) { // 如果需要填充,则画圆内部的点
for (i = x - dx + 1; i < x + dx; i++) {
draw_point(i, y + dy);
draw_point(i, y - dy);
}
for (i = x - dy + 1; i < x + dy; i++) {
draw_point(i, y + dx);
draw_point(i, y - dx);
}
} else { // 否则只画圆上的点
draw_point(x + dx, y + dy);
draw_point(x + dx, y - dy);
draw_point(x - dx, y + dy);
draw_point(x - dx, y - dy);
draw_point(x + dy, y + dx);
draw_point(x + dy, y - dx);
draw_point(x - dy, y + dx);
draw_point(x - dy, y - dx);
}
}
}
// 画椭圆
void draw_ellipse(unsigned int x, unsigned int y, unsigned int a, unsigned int b, unsigned int fill) {
unsigned int aa = a * a;
unsigned int bb = b * b;
unsigned int aa2 = 2 * aa;
unsigned int bb2 = 2 * bb;
unsigned int x0 = x - a;
unsigned int x1 = x + a;
unsigned int y0 = y - b;
unsigned int y1 = y + b;
unsigned int dx = a / sqrt(aa + bb);
unsigned int dy = dx * b / a;
unsigned int sx = x - dx;
unsigned int ex = x + dx;
unsigned int y2;
if (fill) { // 填充椭圆
for ( y2 = y0 + 1; y2 < y1; y2++) {
int x02 = aa * (y - y0 - 1) * (y - y0 - 1); // (y-1)^2*a^2
int x12 = aa * (y - y1) * (y - y1); // (y- y1)^2*a^2
for (y2 = x02 / aa + x0 + 1; y2 < x1 - x12 / aa - 1; y2++) {
draw_point(x, y);
}
}
} else { // 画椭圆
int x = 0, y = b;
int d = bb - aa * b + aa / 4;
while (bb2 * x < aa2 * y) {
draw_point(x0 + x, y0 + y);
draw_point(x1 - x, y0 + y);
draw_point(x0 + x, y1 - y);
draw_point(x1 - x, y1 - y);
if (d < 0) {
x++;
d += bb2 * x + bb;
} else {
x++;
y--;
d += bb2 * x - aa2 * y + bb;
}
}
d = bb * (x + 0.5) * (x + 0.5) + aa * (y - 1) * (y - 1) - aa * bb;
while (y >= 0) {
draw_point(x0 + x, y0 + y);
draw_point(x1 - x, y0 + y);
draw_point(x0 + x, y1 - y);
draw_point(x1 - x, y1 - y);
if (d > 0) {
y--;
d += aa - aa2 * y;
} else {
x++;
y--;
d += bb2 * x - aa2 * y + aa;
}
}
}
}
// 画三角形
void draw_triangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int x3, unsigned int y3, unsigned int fill) {
if (fill) { // 填充三角形
int x, y, x_min, x_max, y_min, y_max;
x_min = x1 < x2 ? (x1 < x3 ? x1 : x3) : (x2 < x3 ? x2 : x3);
x_max = x1 > x2 ? (x1 > x3 ? x1 : x3) : (x2 > x3 ? x2 : x3);
y_min = y1 < y2 ? (y1 < y3 ? y1 : y3) : (y2 < y3 ? y2 : y3);
y_max = y1 > y2 ? (y1 > y3 ? y1 : y3) : (y2 > y3 ? y2 : y3);
for (x = x_min + 1; x < x_max; x++) { // 遍历所有可能的点
for (y = y_min + 1; y < y_max; y++) {
if (((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)) * ((x3 - x2) * (y - y2) - (y3 - y2) * (x - x2)) * ((x1 - x3) * (y - y3) - (y1 - y3) * (x - x3)) < 0) { // 用叉积判断当前点是否在三角形内部
draw_point(x, y); // 如果在则画点
}
}
}
} else { // 画三角形
draw_line(x1, y1, x2, y2);
draw_line(x2, y2, x3, y3);
draw_line(x3, y3, x1, y1);
}
}
// 显示画布
void showcanvas() {
unsigned int y ,x;
for ( y = 0; y < HEIGHT; y++) {
for (x = 0; x < WIDTH; x++) {
putchar(is_point_drawn(x, y) ? '1' : '0'); // 根据点是否被画来输出 0 或 1
} putchar('\n');
}
}
void clearScreen(){
unsigned i,j;
for(i=0;i<WIDTH;i++)
for(j=0;j<HEIGHT/8;j++)
{
canvas[i*8+j]=0x00;
}
}
3.绘图功能头文件:draw.h
关于画图的函数都在这里了:
#ifndef __CANVAS_DRAW_H__
#define _CANVAS_DRAW_H__
/
//
// 画图程序 by AI,20230508
//
#define WIDTH 16 // 画布宽度
#define HEIGHT 64 // 画布高度
// 画点
void draw_point(unsigned int x, unsigned int y);
// 擦除点
void erase_point(unsigned int x, unsigned int y) ;
// 判断点是否被画
unsigned int is_point_drawn(unsigned int x, unsigned int y);
// 画线
void draw_line(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) ;
// 画矩形
void draw_rectangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int fill);
// 画圆
void draw_circle(unsigned int x, unsigned int y, unsigned int r, unsigned int fill);
// 画椭圆
void draw_ellipse(unsigned int x, unsigned int y, unsigned int a, unsigned int b, unsigned int fill) ;
// 画三角形
void draw_triangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int x3, unsigned int y3, unsigned int fill) ;
// 显示画布
void showcanvas() ;
void clearScreen();
/
#endif
总结
1、如果有不会写的功能可以问AI。AI太强大,写出的代码效率很高,思路清晰,就是有些算法看不懂。
2、部分函数可能存在问题。