下午好,几天前,我完成了一个用ATMEGA328p微控制器编程的7x10 led矩阵的个人项目。为了控制矩阵,我使用2 74HC595移位寄存器级联在10列和CD4017十年计数器在7行。所述LED矩阵的8行独立地连接到2N3904 NPN晶体管,该晶体管提供一条接地路径以将所有LED的合并电流汇到一行中。我已经对微控制器进行了几个月的编程,我希望得到一些改进我的代码的建议,无论是在良好的实践方面还是在程序优化方面。
代码:
#define CD4017_CLK PORTB1
#define CD4017_RST PORTB2
#define SERIAL_DATA PORTB3
#define ST_CLK PORTB4
#define SH_CLK PORTB5
#define MAX_CHARS 100
#define DELAY 30
#define SHIFT_STEP 1
/*
char_data is a two dimensional constant array that holds the 5-bit column values
of individual rows for ASCII characters that are to be displayed on a 7x10 matrix.
*/
const byte char_data[95][7]={
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // space
{0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x4}, // !
{0xA, 0xA, 0xA, 0x0, 0x0, 0x0, 0x0}, // "
{0xA, 0xA, 0x1F, 0xA, 0x1F, 0xA, 0xA}, // #
{0x4, 0xF, 0x14, 0xE, 0x5, 0x1E, 0x4}, // $
{0x18, 0x19, 0x2, 0x4, 0x8, 0x13, 0x3}, // %
{0x8, 0x14, 0x14, 0x8, 0x15, 0x12, 0xD}, // &
{0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0}, // '
{0x4, 0x8, 0x10, 0x10, 0x10, 0x8, 0x4}, // (
{0x4, 0x2, 0x1, 0x1, 0x1, 0x2, 0x4}, // )
{0x4, 0x15, 0xE, 0x4, 0xE, 0x15, 0x4}, // *
{0x0, 0x4, 0x4, 0x1F, 0x4, 0x4, 0x0}, // +
{0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x8}, // ,
{0x0, 0x0, 0x0, 0x1F, 0x0, 0x0, 0x0}, // -
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4}, // .
{0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x0}, // /
{0xE, 0x11, 0x13, 0x15, 0x19, 0x11, 0xE}, // 0
{0x6, 0xC, 0x4, 0x4, 0x4, 0x4, 0xE}, // 1
{0xE, 0x11, 0x1, 0x6, 0x8, 0x10, 0x1F}, // 2
{0x1F, 0x1, 0x2, 0x6, 0x1, 0x11, 0xF}, // 3
{0x2, 0x6, 0xA, 0x12, 0x1F, 0x2, 0x2}, // 4
{0x1F, 0x10, 0x1E, 0x1, 0x1, 0x11, 0xE}, // 5
{0xE, 0x11, 0x10, 0x1E, 0x11, 0x11, 0xE}, // 6
{0x1F, 0x1, 0x2, 0x4, 0x8, 0x8, 0x8}, // 7
{0xE, 0x11, 0x11, 0xE, 0x11, 0x11, 0xE}, // 8
{0xE, 0x11, 0x11, 0xF, 0x1, 0x11, 0xE}, // 9
{0x0, 0x0, 0x4, 0x0, 0x4, 0x0, 0x0}, // :
{0x0, 0x0, 0x4, 0x0, 0x4, 0x4, 0x8}, // ;
{0x2, 0x4, 0x8, 0x10, 0x8, 0x4, 0x2}, // <
{0x0, 0x0, 0x1F, 0x0, 0x1F, 0x0, 0x0}, // =
{0x8, 0x4, 0x2, 0x1, 0x2, 0x4, 0x8}, // >
{0xE, 0x11, 0x1, 0x2, 0x4, 0x0, 0x4}, // ?
{0xE, 0x11, 0x17, 0x15, 0x16, 0x10, 0xF}, // @
{0xE, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // A
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B
{0xE, 0x11, 0x10, 0x10, 0x10, 0x11, 0xE}, // C
{0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E}, // D
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // E
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10}, // F
{0xE, 0x11, 0x10, 0x17, 0x15, 0x11, 0xE}, // G
{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // H
{0x1F, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1F}, // I
{0x1, 0x1, 0x1, 0x1, 0x1, 0x11, 0xE}, // J
{0x11, 0x11, 0x12, 0x1C, 0x12, 0x11, 0x11}, // K
{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}, // L
{0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // M
{0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11}, // N
{0xE, 0x11, 0x11, 0x11, 0x11, 0x11, 0xE}, // O
{0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, // P
{0xE, 0x11, 0x11, 0x11, 0x15, 0x13, 0xF}, // Q
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x11}, // R
{0xF, 0x10, 0x10, 0xE, 0x1, 0x1, 0x1E}, // S
{0x1F, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4}, // T
{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0xE}, // U
{0x11, 0x11, 0x11, 0x11, 0x11, 0xA, 0x4}, // V
{0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11}, // W
{0x11, 0x11, 0xA, 0x4, 0xA, 0x11, 0x11}, // X
{0x11, 0x11, 0xA, 0x4, 0x4, 0x4, 0x4}, // Y
{0x1F, 0x1, 0x2, 0x4, 0x8, 0x10, 0x1F}, // Z
{0xE, 0x8, 0x8, 0x8, 0x8, 0x8, 0xE}, // [
{0x0, 0x10, 0x8, 0x4, 0x2, 0x1, 0x0}, // backward slash
{0xE, 0x2, 0x2, 0x2, 0x2, 0x2, 0xE}, // ]
{0x0, 0x0, 0x4, 0xA, 0x11, 0x0, 0x0}, // ^
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F}, // _
{0x8, 0x4, 0x2, 0x0, 0x0, 0x0, 0x0}, // `
{0x0, 0x0, 0xE, 0x1, 0xF, 0x11, 0xF}, // a
{0x10, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x1E}, // b
{0x0, 0x0, 0xE, 0x11, 0x10, 0x11, 0xE}, // c
{0x1, 0x1, 0x1, 0xF, 0x11, 0x11, 0xF}, // d
{0x0, 0x0, 0xE, 0x11, 0x1F, 0x10, 0xF}, // e
{0xE, 0x9, 0x1C, 0x8, 0x8, 0x8, 0x8}, // f
{0x0, 0x0, 0xE, 0x11, 0xF, 0x1, 0xE}, // g
{0x10, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x11}, // h
{0x4, 0x0, 0x4, 0x4, 0x4, 0x4, 0xE}, // i
{0x1, 0x0, 0x3, 0x1, 0x1, 0x11, 0xE}, // j
{0x10, 0x10, 0x11, 0x12, 0x1C, 0x12, 0x11}, // k
{0xC, 0x4, 0x4, 0x4, 0x4, 0x4, 0xE}, // l
{0x0, 0x0, 0x1E, 0x15, 0x15, 0x15, 0x15}, // m
{0x0, 0x0, 0x1E, 0x11, 0x11, 0x11, 0x11}, // n
{0x0, 0x0, 0xE, 0x11, 0x11, 0x11, 0xE}, // o
{0x0, 0x0, 0xF, 0x9, 0xE, 0x8, 0x8}, // p
{0x0, 0x0, 0xF, 0x11, 0xF, 0x1, 0x1}, // q
{0x0, 0x0, 0x17, 0x18, 0x10, 0x10, 0x10}, // r
{0x0, 0x0, 0xF, 0x10, 0xE, 0x1, 0x1E}, // s
{0x4, 0x4, 0xE, 0x4, 0x4, 0x4, 0x3}, // t
{0x0, 0x0, 0x11, 0x11, 0x11, 0x13, 0x13}, // u
{0x0, 0x0, 0x11, 0x11, 0x11, 0xA, 0x4}, // v
{0x0, 0x0, 0x11, 0x11, 0x15, 0x1F, 0x15}, // w
{0x0, 0x0, 0x11, 0xA, 0x4, 0xA, 0x11}, // x
{0x0, 0x0, 0x11, 0x11, 0xF, 0x1, 0x1E}, // y
{0x0, 0x0, 0x1F, 0x2, 0x4, 0x8, 0x1F}, // z
{0x2, 0x4, 0x4, 0x8, 0x4, 0x4, 0x2}, // {
{0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4}, // |
{0x8, 0x4, 0x4, 0x2, 0x4, 0x4, 0x8}, // }
{0x0, 0x0, 0x0, 0xA, 0x15, 0x0, 0x0}, // ~
};
uint16_t frame_buffer[7];
char message[MAX_CHARS+2] = "MATRIZ LED 7X10 ";
int string_length = strlen(message);
void send_data(uint16_t data){
uint16_t mask = 1, flag = 0;
for (byte i=0; i<10; i++){
flag = data & mask;
if (flag) PORTB |= (1 << SERIAL_DATA);
else PORTB &= ~(1 << SERIAL_DATA);
PORTB |= (1 << SH_CLK);
PORTB &= ~(1 << SH_CLK);
mask <<= 1;
}
PORTB |= (1 << ST_CLK);
PORTB &= ~(1 << ST_CLK);
}
void send_frame_buffer(){
for (byte t=0; t<DELAY; t++){ // The delay we get with loops.
for (byte row=0; row<7; row++){ // For each row.
// Send 10 bits to shift registers.
send_data(frame_buffer[row]);
// This delay defines the time to play each pattern.
delayMicroseconds(800);
// Clear the row so we can go on to the next row without smearing.
send_data(0);
// On to the next row.
PORTB |= (1 << CD4017_CLK);
PORTB &= ~(1 << CD4017_CLK);
}
// Select the first row.
PORTB |= (1 << CD4017_RST);
PORTB &= ~(1 << CD4017_RST);
}
}
void display_message(){
for (byte c=0; c<string_length; c++){ // For each character.
byte mask = 0x10;
for (byte column=0; column<5; column++){ // For each column.
for (byte row=0; row<7; row++){ // For each row.
// To obtain the position of the current character in the char_data array, subtract 32 from the ASCII value of the character itself.
byte index = message[c];
byte temp = char_data[index-32][row];
frame_buffer[row] = (frame_buffer[row]<<1) | ((temp&mask)>>4-column);
}
send_frame_buffer();
mask >>= 1;
}
// One column of separation between characters.
for (byte row=0; row<7; row++){
frame_buffer[row] <<= SHIFT_STEP;
}
send_frame_buffer();
}
}
int main(){
// PORTB as output.
DDRB = 0b111111;
// Makes sure the 4017 value is 0.
PORTB |= (1 << CD4017_RST);
PORTB &= ~(1 << CD4017_RST);
while (true){
display_message();
}
}发布于 2022-11-06 21:57:43
代码一点也不坏,所以大多数情况下,这些都是关于样式的注释,不会对底层机器代码产生重大影响,我们将看到。
这句话有点奇怪:
char message[MAX_CHARS+2] = "MATRIZ LED 7X10 ";很明显,MAX_CHARS并不是指MAX_CHARS?我建议简单地将MAX_CHARS的值增加两倍。就不会那么奇怪了。
添加函数
当前代码包括此函数。
void send_data(uint16_t data){
uint16_t mask = 1, flag = 0;
for (byte i=0; i<10; i++){
flag = data & mask;
if (flag) PORTB |= (1 << SERIAL_DATA);
else PORTB &= ~(1 << SERIAL_DATA);
PORTB |= (1 << SH_CLK);
PORTB &= ~(1 << SH_CLK);
mask <<= 1;
}
PORTB |= (1 << ST_CLK);
PORTB &= ~(1 << ST_CLK);
}当然,这是可行的,但它很容易出错。当您指的是|=时,输入&=或忘记~是很容易的。此外,处理时钟从高到低的代码行必须成对操作。出于所有这些原因,我宁愿这样写:
void send_data(uint16_t data){
for (uint16_t mask{1}; mask & 0x3ff; mask <<= 1) {
bit(SERIAL_DATA, data & mask);
bithilo(SH_CLK);
}
bithilo(ST_CLK);
}您可以使用Arduino平台提供的digitalWrite函数,也可以像我在这里所做的那样创建更高效的函数:
inline void setbit(int bitnum) {
PORTB |= (1 << bitnum);
}
inline void clrbit(int bitnum) {
PORTB &= ~(1 << bitnum);
}
inline void bit(int bitnum, bool val) {
if (val)
setbit(bitnum);
else
clrbit(bitnum);
}
inline void bithilo(int bitnum) {
setbit(bitnum);
clrbit(bitnum);
}有人可能会反对它是更多的代码,或者它会使程序变慢,但实际上,作为这个在线示例显示,这两个版本产生相同的汇编语言指令序列。
将这些函数标记为inline是向编译器建议的一种方法,即这段代码的内联替换可能比将代码作为子例程实际调用更好。在这种特殊情况下,它允许编译器的优化器认识到可以使用非常高效的sbi或cbi汇编语言指令来完成操作。
这句话:for (uint16_t mask{1}; mask & 0x3ff; mask <<= 1)一开始看起来很混乱,但它是一个常见的成语,尤其是对于嵌入式系统。我们想一次提取一位,十位。0x3ff是一个设置了低10位的uint16_t。当我们将位移到左边时,mask & 0x3ff的值将是一个非零的数字,直到我们到达第11位为止。此时,表达式为0,C++将其解释为false,因此循环结束。
正如您可以从在线编译器演示中看到的那样,这两个版本都会产生相同的代码,因此我主张使用您认为最容易阅读和维护的代码。
下面是“0”字符的代码,但其中有一个错误:
{0xE, 0x11, 0x13, 0x15, 0x1B, 0x11, 0xE}, // 0 but with an error你需要多长时间才能发现错误?我建议做两件事之一。要么使用运行在计算机上的外部程序(而不是Arduino)生成可视化和十六进制表单,要么以二进制方式编写数据,以便更容易地查看所发生的事情:
{ 0b01110,
0b10001,
0b10011,
0b10101,
0b11011, // <-- this line has the error
0b10001,
0b01110
},这仍然不是很好,但我认为,对于一个人来说,要验证所有行都是5位,2)每个字符都有一个合理的表示法要容易得多。考虑一下,如果您被要求更改此字符,使其没有对角线在其中。在这种形式下会简单得多。
Arduino的语言实际上是一个C++变体,它具有一些非常有用的面向对象特性。例如,您可以将显示包装到一个对象中,以便如果您想要更改它以容纳两个10x7显示器,并行显示作为20x7显示器,则代码其余部分的接口可以保持不变。
https://codereview.stackexchange.com/questions/281001
复制相似问题