1/*
2
3MIT License
4
5Copyright (c) 2021 David Schramm
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24*/
25
26#include <pico/stdlib.h>
27#include <hardware/i2c.h>
28#include <pico/binary_info.h>
29#include <stdlib.h>
30#include <string.h>
31#include <stdio.h>
32
33#include "ssd1306.h"
34#include "font.h"
35
36inline static void swap(uint32_t *a, uint32_t *b) {
37 uint32_t *t=a;
38 *a=*b;
39 *b=*t;
40}
41
42inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) {
43 switch(i2c_write_blocking(i2c, addr, src, len, false)) {
44 case PICO_ERROR_GENERIC:
45 printf("[%s] addr not acknowledged!\n", name);
46 break;
47 case PICO_ERROR_TIMEOUT:
48 printf("[%s] timeout!\n", name);
49 break;
50 default:
51 //printf("[%s] wrote successfully %lu bytes!\n", name, len);
52 break;
53 }
54}
55
56inline static void ssd1306_write(ssd1306_t *p, uint8_t val) {
57 uint8_t d[2]= {0x00, val};
58 fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write");
59}
60
61bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance) {
62 p->width=width;
63 p->height=height;
64 p->pages=height/8;
65 p->address=address;
66
67 p->i2c_i=i2c_instance;
68
69
70 p->bufsize=(p->pages)*(p->width);
71 if((p->buffer=malloc(p->bufsize+1))==NULL) {
72 p->bufsize=0;
73 return false;
74 }
75
76 ++(p->buffer);
77
78 // from https://github.com/makerportal/rpi-pico-ssd1306
79 int8_t cmds[]= {
80 SET_DISP | 0x00, // off
81 // address setting
82 SET_MEM_ADDR,
83 0x00, // horizontal
84 // resolution and layout
85 SET_DISP_START_LINE | 0x00,
86 SET_SEG_REMAP | 0x01, // column addr 127 mapped to SEG0
87 SET_MUX_RATIO,
88 height - 1,
89 SET_COM_OUT_DIR | 0x08, // scan from COM[N] to COM0
90 SET_DISP_OFFSET,
91 0x00,
92 SET_COM_PIN_CFG,
93 width>2*height?0x02:0x12,
94 // timing and driving scheme
95 SET_DISP_CLK_DIV,
96 0x80,
97 SET_PRECHARGE,
98 p->external_vcc?0x22:0xF1,
99 SET_VCOM_DESEL,
100 0x30, // 0.83*Vcc
101 // display
102 SET_CONTRAST,
103 0xFF, // maximum
104 SET_ENTIRE_ON, // output follows RAM contents
105 SET_NORM_INV, // not inverted
106 // charge pump
107 SET_CHARGE_PUMP,
108 p->external_vcc?0x10:0x14,
109 SET_DISP | 0x01
110 };
111
112 for(size_t i=0; i<sizeof(cmds); ++i)
113 ssd1306_write(p, cmds[i]);
114
115 return true;
116}
117
118inline void ssd1306_deinit(ssd1306_t *p) {
119 free(p->buffer-1);
120}
121
122inline void ssd1306_poweroff(ssd1306_t *p) {
123 ssd1306_write(p, SET_DISP|0x00);
124}
125
126inline void ssd1306_poweron(ssd1306_t *p) {
127 ssd1306_write(p, SET_DISP|0x01);
128}
129
130inline void ssd1306_contrast(ssd1306_t *p, uint8_t val) {
131 ssd1306_write(p, SET_CONTRAST);
132 ssd1306_write(p, val);
133}
134
135inline void ssd1306_invert(ssd1306_t *p, uint8_t inv) {
136 ssd1306_write(p, SET_NORM_INV | (inv & 1));
137}
138
139inline void ssd1306_clear(ssd1306_t *p) {
140 memset(p->buffer, 0, p->bufsize);
141}
142
143void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) {
144 if(x>=p->width || y>=p->height) return;
145
146 p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8
147}
148
149void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
150 if(x1>x2) {
151 swap(&x1, &x2);
152 swap(&y1, &y2);
153 }
154
155 if(x1==x2) {
156 if(y1>y2)
157 swap(&y1, &y2);
158 for(int32_t i=y1; i<=y2; ++i)
159 ssd1306_draw_pixel(p, x1, i);
160 return;
161 }
162
163 float m=(float) (y2-y1) / (float) (x2-x1);
164
165 for(int32_t i=x1; i<=x2; ++i) {
166 float y=m*(float) (i-x1)+(float) y1;
167 ssd1306_draw_pixel(p, i, (uint32_t) y);
168 }
169}
170
171void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
172 for(uint32_t i=0; i<width; ++i)
173 for(uint32_t j=0; j<height; ++j)
174 ssd1306_draw_pixel(p, x+i, y+j);
175
176}
177
178void ssd13606_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
179 ssd1306_draw_line(p, x, y, x+width, y);
180 ssd1306_draw_line(p, x, y+height, x+width, y+height);
181 ssd1306_draw_line(p, x, y, x, y+height);
182 ssd1306_draw_line(p, x+width, y, x+width, y+height);
183}
184
185void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c) {
186 if(c > '~')
187 return;
188
189 for(uint8_t i=0; i<font[1]; ++i) {
190 uint8_t line=(uint8_t)(font[(c-0x20)*font[1]+i+2]);
191
192 for(int8_t j=0; j<font[0]; ++j, line>>=1) {
193 if(line & 1 ==1)
194 ssd1306_draw_square(p, x+i*scale, y+j*scale, scale, scale);
195 }
196 }
197}
198
199void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char *s) {
200 for(int32_t x_n=x; *s; x_n+=font[0]*scale) {
201 ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++));
202 }
203}
204
205void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c) {
206 ssd1306_draw_char_with_font(p, x, y, scale, font_8x5, c);
207}
208
209void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char *s) {
210 ssd1306_draw_string_with_font(p, x, y, scale, font_8x5, s);
211}
212
213static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) {
214 switch(size) {
215 case 1:
216 return data[offset];
217 case 2:
218 return data[offset]|(data[offset+1]<<8);
219 case 4:
220 return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24);
221 default:
222 __builtin_unreachable();
223 }
224 __builtin_unreachable();
225}
226
227void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) {
228 if(size<54) // data smaller than header
229 return;
230
231 const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4);
232 const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4);
233 const int32_t biWidth=(int32_t) ssd1306_bmp_get_val(data, 18, 4);
234 const int32_t biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4);
235 const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2);
236 const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4);
237
238 if(biBitCount!=1) // image not monochrome
239 return;
240
241 if(biCompression!=0) // image compressed
242 return;
243
244 const int table_start=14+biSize;
245 uint8_t color_val;
246
247 for(uint8_t i=0; i<2; ++i) {
248 if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) {
249 color_val=i;
250 break;
251 }
252 }
253
254 uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0);
255 if(bytes_per_line&3)
256 bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4;
257
258 const uint8_t *img_data=data+bfOffBits;
259
260 int step=biHeight>0?-1:1;
261 int border=biHeight>0?-1:biHeight;
262 for(uint32_t y=biHeight>0?biHeight-1:0; y!=border; y+=step) {
263 for(uint32_t x=0; x<biWidth; ++x) {
264 if(((img_data[x>>3]>>(7-(x&7)))&1)==color_val)
265 ssd1306_draw_pixel(p, x_offset+x, y_offset+y);
266 }
267 img_data+=bytes_per_line;
268 }
269}
270
271inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) {
272 ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0);
273}
274
275void ssd1306_show(ssd1306_t *p) {
276 uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1};
277 if(p->width==64) {
278 payload[1]+=32;
279 payload[2]+=32;
280 }
281
282 for(size_t i=0; i<sizeof(payload); ++i)
283 ssd1306_write(p, payload[i]);
284
285 *(p->buffer-1)=0x40;
286
287 fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show");
288}