Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'C'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona


TempX

  1. Witam was serdecznie. Mam pytanie. Chciałbym użyć wyrażeń regularnych regex. Dodaje #include <regex.h> i próbuje użyć funkcji regcomp lub regexec. Niestety po kompilacji otrzymuję błąd "undefined reference to regcomp". Co muszę zrobić aby regex działał w CubeIDE w wersji 1.11.0? Znalazłem taką bibliotekę https://github.com/kokke/tiny-regex-c tylko nie wiem za bardzo jak mam ją wrzucić do CubeIDE? Czy ta biblioteka ma sens i czy są jakieś inne rozwiązania dotyczące wyrażeń regularnych dla CubeIDE?
  2. Cześć, mając przetwornik A/C (np. MCP3426) na wyjściu otrzymuję liczbę stałoprzecinkową. Co, jeśli dodam do siebie 4 wyników pomiarów i podzielę ich sumę przez 4. Może się zdarzyć tak, że wynik będzie zmiennoprzecinkowy. Tu, już wchodzimy na arytmetykę zmiennoprzecinkową, często wolniejszą dla mikrokontrolera. Gdy patrzę na ten problem, zastanawiam się, czy jest sens w ogóle uśredniać w takim wypadku. Jak to rozwiązujecie?
  3. Potrzebowałem na szybko biblioteki dla wyświetlacza OLED 1.5" Wziąłem pierwszą z brzegu: https://github.com/hexaguin/SSD1327 i przystosowałem ją aby działała na mikrokontroler STM32L476 z HAL: .h #ifndef SSD1327_SSD1327_H_ #define SSD1327_SSD1327_H_ #include "main.h" #include "stdbool.h" #include "spi.h" // Scroll rate constants. See datasheet page 40. #define SSD1327_SCROLL_2 0b111 #define SSD1327_SCROLL_3 0b100 #define SSD1327_SCROLL_4 0b101 #define SSD1327_SCROLL_5 0b110 #define SSD1327_SCROLL_6 0b000 #define SSD1327_SCROLL_32 0b001 #define SSD1327_SCROLL_64 0b010 #define SSD1327_SCROLL_256 0b011 // void writeCmd(uint8_t reg); void writeData(uint8_t data); void setWriteZone(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); uint16_t coordsToAddress(uint8_t x, uint8_t y); void setPixelChanged(uint8_t x, uint8_t y, bool changed); void SSD1327drawPixel(uint8_t x, uint8_t y, uint8_t color, bool display); void SSD1327drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color, bool display); void SSD1327drawHLine(int x, int y, int length, uint8_t color, bool display); void SSD1327drawVLine(int x, int y, int length, uint8_t color, bool display); void SSD1327drawLine(int x1, int y1, int x2, int y2, uint8_t color, bool display); void SSD1327drawByteAsRow(uint8_t x, uint8_t y, uint8_t byte, uint8_t color); void SSD1327drawChar(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawChar16(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawChar32(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawCharArray(uint8_t x, uint8_t y, char text[], uint8_t color, int size); void SSD1327drawString(uint8_t x, uint8_t y, char * textString, uint8_t color, int size); void setupScrolling(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, uint8_t scrollSpeed, bool right); void startScrolling(); void stopScrolling(); void scrollStep(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, bool right); void fillStripes(uint8_t offset); void SSD1327clearBuffer(); void SSD1327writeFullBuffer(); void writeUpdates(); void SSD1327setContrast(uint8_t contrast); void SSD1327initRegs(); void SSD1327init(SPI_HandleTypeDef * hspi); extern uint8_t frameBuffer[8192]; // Should mirror the display's own frameBuffer. extern uint8_t changedPixels[1024]; // Each bit of this array represets whether a given byte of frameBuffer (e.g. a pair of pixels) is not up to date. #endif /* SSD1327_SSD1327_H_ */ .c /* * ssd1327.c * * Created on: Apr 22, 2022 * Author: dell */ #include "main.h" #include "spi.h" #include "dma.h" #include "ssd1327.h" #include "stdbool.h" #include "stdlib.h" #include "math.h" #include "font8x8_basic.h" #include "font16x16.h" #include "font16x32.h" #define CS_SET HAL_GPIO_WritePin(SSD_CS_GPIO_Port, SSD_CS_Pin,GPIO_PIN_SET) #define CS_RESET HAL_GPIO_WritePin(SSD_CS_GPIO_Port, SSD_CS_Pin,GPIO_PIN_RESET) #define DC_SET HAL_GPIO_WritePin(SSD_DC_GPIO_Port,SSD_DC_Pin,GPIO_PIN_SET) #define DC_RESET HAL_GPIO_WritePin(SSD_DC_GPIO_Port,SSD_DC_Pin,GPIO_PIN_RESET) #define RST_SET HAL_GPIO_WritePin(SSD_RST_GPIO_Port,SSD_RST_Pin,GPIO_PIN_SET) #define RST_RESET HAL_GPIO_WritePin(SSD_RST_GPIO_Port,SSD_RST_Pin,GPIO_PIN_RESET) #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) uint8_t frameBuffer[8192]; // Should mirror the display's own frameBuffer. uint8_t changedPixels[1024]; SPI_HandleTypeDef * spix; static void bitWrite(uint8_t * x, uint8_t n, bool b); static uint8_t bitRead(uint8_t x,uint8_t n) { return(x & (1<<n)); } // bitWrite(x, 0, 1); // write 1 to the first bit of x static void ssdDelay(uint32_t d); static void SSD1327writeCmd(uint8_t reg); static void bitWrite(uint8_t * x, uint8_t n, bool b) { b ? (*x|= (1 << n)):(*x&=~(1 << n)); } //void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) //TODO: Find a way to handle the write commands without toggling CS and DC every time void SSD1327writeCmd(uint8_t reg){//Writes a command byte to the driver DC_RESET; // digitalWrite(_dc, LOW); CS_RESET; // SPI.transfer(reg); HAL_SPI_Transmit(spix,&reg,1,500); CS_SET; // digitalWrite(_cs, HIGH); } void SSD1327writeData(uint8_t data){//Writes 1 byte to the display's memory DC_SET; CS_RESET; HAL_SPI_Transmit(spix,&data,1,100); CS_SET; } void SSD1327setWriteZone(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { //defines a rectangular area of memory which the driver will itterate through. This function takes memory locations, meaning a 64x128 space SSD1327writeCmd(0x15); //Set Column Address SSD1327writeCmd(x1); //Beginning. Note that you must divide the column by 2, since 1 byte in memory is 2 pixels SSD1327writeCmd(x2); //End SSD1327writeCmd(0x75); //Set Row Address SSD1327writeCmd(y1); //Beginning SSD1327writeCmd(y2); //End } uint16_t SSD1327coordsToAddress(uint8_t x, uint8_t y){ //Converts a pixel location to a linear memory address return (x/2)+(y*64); } void SSD1327setPixelChanged(uint8_t x, uint8_t y, bool changed){ uint16_t targetByte = SSD1327coordsToAddress(x, y)/8; bitWrite(&changedPixels[targetByte], SSD1327coordsToAddress(x, y) % 8, changed); } void SSD1327drawPixel(uint8_t x, uint8_t y, uint8_t color, bool display){//pixel xy coordinates 0-127, color 0-15, and whether to immediately output it to the display or buffer it int address = SSD1327coordsToAddress(x,y); if((x%2) == 0){//If this is an even pixel, and therefore needs shifting to the more significant nibble frameBuffer[address] = (frameBuffer[address] & 0x0f) | (color<<4); } else { frameBuffer[address] = (frameBuffer[address] & 0xf0) | (color); } if(display){ SSD1327setWriteZone(x/2,y,x/2,y); SSD1327writeData(frameBuffer[address]); SSD1327setPixelChanged(x, y, false); // We've now synced the display with this byte of the buffer, no need to write it again } else { SSD1327setPixelChanged(x, y, true); // This pixel is due for an update next refresh } } void SSD1327drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color, bool display){//Draws a rectangle from x1,y1 to x2,y2. uint8_t xMin = MIN(x1, x2); // TODO: double performance by writing whole bytes at a time uint8_t xMax = MAX(x1, x2); uint8_t yMin = MIN(y1, y2); uint8_t yMax = MAX(y1, y2); for (uint8_t x = xMin; x <= xMax; x++) { for (uint8_t y = yMin; y <= yMax; y++) { SSD1327drawPixel(x, y, color, display); } } } void SSD1327drawHLine(int x, int y, int length, uint8_t color, bool display){ for (uint8_t i = x; i < x+length; i++) { SSD1327drawPixel(i, y, color, display); } } void SSD1327drawVLine(int x, int y, int length, uint8_t color, bool display){ for (uint8_t i = y; i < y+length; i++) { SSD1327drawPixel(x, i, color, display); } } void SSD1327drawLine(int x0, int y0, int x1, int y1, uint8_t color, bool display){ //Bresenham's line algorithm int deltaX = abs(x1-x0); int deltaY = abs(y1-y0); int signX = x0<x1 ? 1 : -1; int signY = y0<y1 ? 1 : -1; int error = (deltaX>deltaY ? deltaX : -deltaY)/2, error2; while (true) { SSD1327drawPixel(x0, y0, color, display); if (x0==x1 && y0==y1) break; error2 = error; if (error2 >-deltaX) { error -= deltaY; x0 += signX; } if (error2 < deltaY) { error += deltaX; y0 += signY; } } } void SSD1327drawByteAsRow(uint8_t x, uint8_t y, uint8_t byte, uint8_t color){//Draws a byte as an 8 pixel row for (int i = 0; i < 8; i++) { if(bitRead(byte, i)){ SSD1327drawPixel(x+i, y, color, false); } } } void SSD1327drawChar(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t i = 0; i < 8; i++) { SSD1327drawByteAsRow(x, y+i, font8x8_basic[(unsigned char)thisChar][i], color); } } void SSD1327drawCharArray(uint8_t x, uint8_t y, char text[], uint8_t color, int size){ const char* thisChar; uint8_t xOffset = 0; if(size==16){ for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar16(x+xOffset, y, *thisChar, color); xOffset += 8; } } else if(size==32){ for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar32(x+xOffset, y, *thisChar, color); xOffset += 16; } } else { for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar(x+xOffset, y, *thisChar, color); xOffset += 8; } } } void SSD1327drawString(uint8_t x, uint8_t y, char * textString, uint8_t color, int size){ // char text[64]; // textString.toCharArray(text, 64); SSD1327drawCharArray(x,y, textString, color, size); } void SSD1327drawChar16(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t row = 0; row < 16; row++) { SSD1327drawByteAsRow(x, y+row, font16x16[(unsigned char)thisChar][row*2], color); SSD1327drawByteAsRow(x+8, y+row, font16x16[(unsigned char)thisChar][(row*2)+1], color); } } void SSD1327drawChar32(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t row = 0; row < 32; row++) { SSD1327drawByteAsRow(x, y+row, font16x32[(unsigned char)thisChar][row*2], color); SSD1327drawByteAsRow(x+8, y+row, font16x32[(unsigned char)thisChar][(row*2)+1], color); } } void SSD1327fillStripes(uint8_t offset){ //gradient test pattern for(int i = 0; i < 8192; i++){ uint8_t color = ((i+offset) & 0xF) | (((i+offset) & 0xF)<<4); frameBuffer[i] = color; } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0xFF; // Set all pixels to be updated next frame. fillStripes should not be used without a full write anyways, but just in case } } void SSD1327setupScrolling(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, uint8_t scrollSpeed, bool right){ uint8_t swap; if (startRow > endRow) { // Ensure start row is before end swap = startRow; startRow = endRow; endRow = swap; } if (startCol > endCol) { // Ditto for columns swap = startCol; startCol = endCol; endCol = swap; } SSD1327writeCmd(0x2E); // Deactivate scrolling before changing anything if (right) { SSD1327writeCmd(0x26); // Scroll right } else { SSD1327writeCmd(0x27); // Scroll left } SSD1327writeCmd(0); // Dummy byte SSD1327writeCmd(startRow); SSD1327writeCmd(scrollSpeed); SSD1327writeCmd(endRow); SSD1327writeCmd(startCol); SSD1327writeCmd(endCol); SSD1327writeCmd(0); // Dummy byte }; void SSD1327startScrolling(){ SSD1327writeCmd(0x2F); } void SSD1327stopScrolling(){ SSD1327writeCmd(0x2E); } void SSD1327scrollStep(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, bool right){ setupScrolling(startRow, endRow, startCol, endCol, SSD1327_SCROLL_2, right); startScrolling(); ssdDelay(15); stopScrolling(); } void SSD1327clearBuffer(){// for(int i = 0; i < 8192; i++){ if (frameBuffer[i]) { // If there is a non-zero (non-black) byte here, make sure it gets updated frameBuffer[i] = 0; bitWrite(&changedPixels[i/8], i%8, 1); // Mark this pixel as needing an update } } } void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display for(int i = 0; i < 8192; i++){ SSD1327writeData(frameBuffer[i]); } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } void SSD1327writeUpdates(){ // Writes only the pixels that have changed to the display for (size_t y = 0; y < 128; y++) { bool continued = false; // If we can continue with the write zone we're using for (size_t x = 0; x < 128; x++) { uint16_t address = coordsToAddress(x, y); if ( bitRead(changedPixels[address/8], address % 8) ) { // If we need an update here if (!continued) { // Just write the byte, no new write zone needed continued = true; setWriteZone(x/2, y, 63, 127); // Set the write zone for this new byte and any subsequent ones } writeData(frameBuffer[address]); bitWrite(&changedPixels[address/8], address % 8, 0); } else { continued = false; // The chain of pixels is broken } } } } void SSD1327setContrast(uint8_t contrast){ SSD1327writeCmd(0x81); //set contrast control SSD1327writeCmd(contrast); //Contrast byte } void SSD1327initRegs(){ //Sends all the boilerplate startup and config commands to the driver SSD1327writeCmd(0xae);//--turn off oled panel SSD1327writeCmd(0x15); //set column addresses SSD1327writeCmd(0x00); //start column 0 SSD1327writeCmd(0x7f); //end column 127 SSD1327writeCmd(0x75); //set row addresses SSD1327writeCmd(0x00); //start row 0 SSD1327writeCmd(0x7f); //end row 127 SSD1327writeCmd(0x81); //set contrast control SSD1327writeCmd(0x80); //50% (128/255) SSD1327writeCmd(0xa0); //gment remap SSD1327writeCmd(0x51); //51 (To my understanding, this is orientation SSD1327writeCmd(0xa1); //start line SSD1327writeCmd(0x00); SSD1327writeCmd(0xa2); //display offset SSD1327writeCmd(0x00); SSD1327writeCmd(0xa4); //rmal display SSD1327writeCmd(0xa8); //set multiplex ratio SSD1327writeCmd(0x7f); SSD1327writeCmd(0xb1); //set phase leghth SSD1327writeCmd(0xf1); SSD1327writeCmd(0xb3); //set dclk SSD1327writeCmd(0x00); //80Hz:0xc1 90Hz:0xe1 100Hz:0x00 110Hz:0x30 120Hz:0x50 130Hz:0x70 01 SSD1327writeCmd(0xab); //Enable vReg SSD1327writeCmd(0x01); SSD1327writeCmd(0xb6); //set phase leghth SSD1327writeCmd(0x0f); SSD1327writeCmd(0xbe); //Set vcomh voltage SSD1327writeCmd(0x0f); SSD1327writeCmd(0xbc); //set pre-charge voltage SSD1327writeCmd(0x08); SSD1327writeCmd(0xd5); //second precharge period SSD1327writeCmd(0x62); SSD1327writeCmd(0xfd); //Unlock commands SSD1327writeCmd(0x12); SSD1327writeCmd(0xAF); ssdDelay(100); } void SSD1327init(SPI_HandleTypeDef * hspi){ spix = hspi; RST_SET; ssdDelay(10); // digitalWrite(_rst, LOW); RST_RESET; //delay(100); ssdDelay(10); RST_SET; ssdDelay(10); SSD1327initRegs(); } static void ssdDelay(uint32_t d) { HAL_Delay(d); }; bibliotekę uruchomiłem, działa, ale widzę że jest sporo bajtów kopiowanych z ram do wyświetlacza, próbowałem to zrobić za pomocą DMA, ja używam SPI2 więc DMA pracuje na kanale 5 tryb normal. W pierszej kolejności próbowałem podmienić zawartość funkcji void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display for(int i = 0; i < 8192; i++){ SSD1327writeData(frameBuffer[i]); } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } W taki sposób: void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display // for(int i = 0; i < 8192; i++){ // SSD1327writeData(frameBuffer[i]); // } DC_SET; CS_RESET; HAL_SPI_Transmit_DMA(&spi2,frameBuffer,8192); CS_SET; for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } W main jest przykładowy kod z biblioteki while (1) { static uint32_t lastTimer = 0; if(HAL_GetTick() > lastTimer) { HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin); lastTimer = HAL_GetTick() + 200; // SSD1327clearBuffer(); // SSD1327drawString(16, 16, "Oled", 0x1, 32); // SSD1327drawString(16, 48, "TEST 1", 0x2, 32); // SSD1327drawString(20, 48+48, "16:32:55", 0xF, 16); // SSD1327setContrast(50); // SSD1327writeFullBuffer(); framecount++; SSD1327clearBuffer(); for (int x = 0; x < 128; x++) { for (int y = (sin(((float)x+framecount)/16)*32)+64; y < 128; y++) { SSD1327drawPixel(x, y, 3, false); } } SSD1327drawCharArray(24, 0, timeText, 0xF, 32); SSD1327drawString(0, 112, "-12.5C", 0xF, 16); SSD1327drawString(84, 112, "52.1%", 0xF, 16); SSD1327writeFullBuffer(); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } Tyle że zamiast delay, użyłem timera programowego. Niestety DMA nie wysyła danych na SPI. Czy jeśli SPI jest skonfigurowane do pracy z DMA to mogę używać go też "normalnie" tj: część danych np ustawienia wysyłać z funkcji a część za pomocą DMA?
  4. Taki prosty kod: #include <avr/io.h> #include <avr/pgmspace.h> const __flash char tab[] = "test"; int main(void) { for(;;); } Kompiluje się ekstra, ale w edytorze mam wciąż błąd składni który nawet nie jest opisany: Zaś konsolka jest czysta 22:01:15 **** Incremental Build of configuration Release for project constFlasz **** make all 'Building file: ../main.c' 'Invoking: AVR Compiler' avr-gcc -Wall -Os -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -std=gnu99 -funsigned-char -funsigned-bitfields -mmcu=atmega16 -DF_CPU=1000000UL -MMD -MP -MF"main.d" -MT"main.o" -c -o "main.o" "../main.c" 'Finished building: ../main.c' ' ' 'Building target: constFlasz.elf' 'Invoking: AVR C Linker' avr-gcc -Wl,-Map,constFlasz.map -mmcu=atmega16 -o "constFlasz.elf" ./main.o 'Finished building target: constFlasz.elf' ' ' 'Invoking: AVR Create Extended Listing' avr-objdump -h -S constFlasz.elf >"constFlasz.lss" 'Finished building: constFlasz.lss' ' ' 'Create Flash image (ihex format)' avr-objcopy -R .eeprom -R .fuse -R .lock -R .signature -O ihex constFlasz.elf "constFlasz.hex" 'Finished building: constFlasz.hex' ' ' 'Create eeprom image (ihex format)' avr-objcopy -j .eeprom --no-change-warnings --change-section-lma .eeprom=0 -O ihex constFlasz.elf "constFlasz.eep" 'Finished building: constFlasz.eep' ' ' 'Invoking: Print Size' avr-size --format=avr --mcu=atmega16 constFlasz.elf AVR Memory Usage ---------------- Device: atmega16 Program: 114 bytes (0.7% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit) 'Finished building: sizedummy' ' ' 22:01:16 Build Finished. 0 errors, 0 warnings. (took 1s.344ms) Ma ktoś pomysł jak wyłączyć tego syntaxa?
  5. Temat związany z biblioteką menu ale założyłem osobny bo wiem że "gryzie" początkujących kolegów. Do rzeczy, mam funkcję która na podstawie podanych wartości węzła i id ma zwrócić wskaźnik do zmiennej która ma akurat być wyświetlona: proszę nie zwracać uwagi na stałe przypisania do iteratorów 'i' i 'j' to w tej chwili niema znaczenia. xx_t * get_props(nodeType_t n,idType_t id) { for(uint8_t i = 0; i < 2 ; i++ ){ if(props[i].nod == n){ // spr czy wezel na liscie for(uint8_t j = 0; j<2;j++){ if(props[i].val[j].id == id)return(xx_t*)&props[i].val[j]; // spr id i zwrot wskaznikka } } } return NULL; // jesli niema } tutaj struktury na których operuje ww funkcja: typedef enum{char_t,uint8t,uint16t,uint32t,int8t,int16t,int32t,rtc_timetypedef,rtc_datetypedef}retTypeEnum; typedef struct{ idType_t id; void * propVal; retTypeEnum retType; }xx_t; typedef struct{ nodeType_t nod; xx_t * val; }menu_properites_t; uzupełnione: xx_t nodeTime[] = { {.id = ZEGAR,.propVal = (RTC_TimeTypeDef*)&time,.retType = rtc_timetypedef}, {.id = DATA,.propVal = (RTC_DateTypeDef*)&date,.retType = rtc_datetypedef} }; xx_t nodeLcd[] = { {.id = LCD_JASNOSC,.propVal = (uint8_t*)&j,.retType = uint8t}, {.id = LCD_KONTRAST,.propVal = (uint8_t*)&b,.retType = uint8t} }; menu_properites_t props[] = { {.nod = 2,.val = nodeTime}, {.nod = 3,.val = nodeLcd} }; I teraz to, o co chcę zapytać: Jak widać powołałem do życia typ enum który określa na jaki typ ma być rzutowana zwracana zmienna, w programie wygląda to tak: if(NULL != get_props(node, id)){ p->flocate(0,5); xx_t * mp = get_props(node, id); if(mp->retType == uint8t) { uint8_t * xa =(uint8_t*)mp->propVal; p->fint(*xa); // wyswitl } if(mp->retType == rtc_timetypedef) { } Kod działa tak że kiedy po odpytaniu za pomocą get_props wynik jest różny od null, mogę wyświetlić zawartość zmiennej spod zwracanego wskaźnika. Warunkiem poprawnego wyświetlenia tej zmiennej jest poprawne rzutowanie na nią. Jak widać w zwracanej wartości jest informacja o tym w jaki sposób program ma ją traktować(enum). No i pytanko do fachowców, jak postrzegacie taki zapis? Czy można to jakoś inaczej, lepiej zapisać? A może przykład jest przekombinowany? Proszę pamiętać że pytanie dotyczy języka C, na C++ kompletnie się nie znam. Liczę że pytanie jest zrozumiałe 🙂
  6. Niby już taki początkujący nie jestem, ale czasem życie ostro to weryfikuje 😉 dostaję warning taki jak w temacie wątku, szukałem troszkę jak to rozwiązać i okazuje się że jest to jakiś błąd kompilatora. Kod - mocno uproszczony - który mi go generuje: .c xx_t x[] = { {.id = 1,.p = (void*)NULL}, {.id = 2,.p = (void*)NULL}, }; prop_t props = {.idx = 0,.val = x}; // <---- w tej linii jest warning nie wiem o którą klamerkę się rzuca. .h typedef struct{ uint8_t id; void * p; }xx_t; typedef struct{ uint8_t idx; xx_t * val[]; }prop_t; Drażni mnie ten warning, może ktoś mi wytłumaczyć jak się go pozbyć?
  7. Piszę sobie takiego małego libsa do obsługi enkodera, do testów używam najzwyklejszego impulsowego oraz płytki nucleo L476. Problem polega na tym że po pewnym czasie kręcenia, funkcja enkodera zacina się. Ponieważ nie mam pomysłu jak to dalej debugować przedstawiam kod testowy. Użyłem timera4 w trybie encodera i pracuje on w pełnej swojej pojemności zliczania. Podejrzewam że problem tkwi w maszynie stanów, być może ktoś zauważy miejsce błędu. #include "main.h" #include "tim.h" #include "stm32l4xx_hal.h" #include "enco.h" typedef enum {idle, count, runf,end}encoState_t; static uint8_t encodir = 0; static uint16_t encoInterval = 50; static void(*enc_event_callback)(void); void enco_init(const uint16_t interval) { encoInterval = interval; } void register_enc_event_callback(void(*callback)(void)){ if(callback)enc_event_callback = callback; } void ENCODER_EVENT() { static uint16_t encoval = 0; static uint32_t tickStart = 0; static encoState_t enc = idle; static uint16_t roznicaPlus, roznicaMinus; if (encoval != __HAL_TIM_GET_COUNTER(&htim4) && enc == idle) { // zaczeto krecic enc = count; // liczenie encoval = __HAL_TIM_GET_COUNTER(&htim4); // roznicaPlus = __HAL_TIM_GET_COUNTER(&htim4) + (ENCO_STEP - 1); roznicaMinus = __HAL_TIM_GET_COUNTER(&htim4) - (ENCO_STEP - 1); tickStart = HAL_GetTick() + encoInterval; // timeout return; } if (enc == count) { if (roznicaPlus == __HAL_TIM_GET_COUNTER(&htim4)) { encodir = encoRight; encoval = __HAL_TIM_GET_COUNTER(&htim4); enc = idle; if (enc_event_callback)enc_event_callback(); }else if (roznicaMinus == __HAL_TIM_GET_COUNTER(&htim4)) { encodir = encoLeft; encoval = __HAL_TIM_GET_COUNTER(&htim4); enc = idle; if (enc_event_callback)enc_event_callback(); // wykonanie przypisanej f } } else if (enc == count && HAL_GetTick() > tickStart) { // timeout encoval = __HAL_TIM_GET_COUNTER(&htim4); // przypisanie aby nie zmenial statusu bez potrzeby enc = idle; } } encoDir getEncoDir(void){ return encodir; // zwraca kierunek obracania } .h #ifndef INC_ENCO_H_ #define INC_ENCO_H_ #define ENCO_STEP 4 //4 or 2 typedef enum{encoLeft = 16,encoRight = 32}encoDir; encoDir getEncoDir(void); void enco_init(const uint16_t interval); void ENCODER_EVENT(); void register_enc_event_callback(void(*callback)(void)); #endif /* INC_ENCO_H_ */ main.c /* USER CODE BEGIN 2 */ enco_init(500); register_enc_event_callback(encoclb); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { ENCODER_EVENT(); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } Oraz zarejestrowana funkcja obsługi callbacka: void encoclb(){ HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); }
  8. Czy jest możliwość tak obudować typ wyliczeniowy aby był widoczny tylko w zakresie struktury, lub nowego powołanego typu danych? Np: #include<stdio.h> typedef struct { enum {aa,ii,dd}cci ;}cc_t; int main() { printf("enum = %i", ii); } Chciałbym aby wartość "ii"(w ogóle to co znajduje się w enum) była widoczna tylko w zakresie typu cc_t, tymczasem program potrafi "printować wartość ii
  9. Od kilku dni przeglądam SDK Raspberry Pi Pico. W pliku nagłówkowym o nazwie "address_mapped.h", który znajduje się w katalogu (ścieżka względna): "...\pico-sdk-master\src\rp2_common\hardware_base\include\hardware", natrafiłem na wiersz kodu, którego nie mogę rozgryźć. Oto on: *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; Plik "address_mapped.h" jest dostępny pod adresem: https://raspberrypi.github.io/pico-sdk-doxygen/address__mapped_8h_source.html. Przytoczony wiersz pochodzi z funkcji: "hw_set_bits". Jej kod to: __force_inline static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; } W tym wierszu występuje "funkcja" o nazwie "hw_set_alias_untyped", która w rzeczywistości jest makrem (jest umieszczone w tym samym pliku). Pytanie jest następujące: do czego jest przypisywana wartość argumentu "mask"? Po lewej stronie występuje makro "hw_set_alias_untyped", które zostanie zastąpione przez: "((uintptr_t)(addr))". Po jego podstawieniu i rozwinięciu powstanie wyrażenie (o ile nie popełniłem błędu): *(io_rw_32 *) ((uintptr_t)(volatile void *) addr) = mask; Argument "addr" jest adresem rejestru RP2040, odwzorowanego w pamięci. Ten adres ("addr") jest rzutowany na wskaźnik amorficzny ("volatile void *") a następnie na wskaźnik "uintptr_t" (znów: o ile nie popełniłem błędu). Żeby analiza była łatwiejsza można to uprościć do: wynik_makra = ((uintptr_t)(volatile void *) addr) // uproszczenie, które mam na myśli *(io_rw_32 *) wynik_makra = mask; // wynikowe wyrażenie po uproszczeniu Czy wynik makra to jakaś zmienna anonimowa (bez nazwy)? Czy lewa część wyrażenia, to dereferencja wskaźnika typu "io_rw_32", którego zawartość jest wyliczana z tego wyrażenia? Czy oznacza to, że zawartość argumentu "mask" jest przypisywana do "tego czegoś", co powstaje po dereferencji? Czy ktoś potrafi to wyjaśnić? Bo ja chyba jednak błędnie wytłumaczyłem sobie ten wiersz kodu.
  10. Cześć, poszukuję przykładów w języku C dla tablic, gdzie do pól mogę się odwołać przez klucz. W javie istnieje coś takiego jak typ map, gdzie zarówno klucz jak i wartość mogą przyjmować dowolny typ danych. W C trzeba to napisać od podstaw, lub skorzystać z gotowych przykładów ale coś mało jest w tym temacie materiałów, a jeśli już są to korzystają z C++ gdzie jest nawet biblioteka wbudowana <map.h> EDIT zapomniałem dorzucić link do tego co już znalazłem: https://ucgosu.pl/2019/07/tablice-przyspieszajace-wyszukiwanie-elementow/ oraz link z artykułu https://github.com/jamesroutley/write-a-hash-table
  11. Cześć, chciałbym zlecić napisanie programu sterującego modelem łódki. Łódka dwusilnikowa (silniki szczotkowe klasy 500 + regulatory ESC). Program ma mieć możliwość wpisania na sztywno koordynatów GPS czyli załóżmy: 50.365044428151755, 18.562147930177403 Po uruchomieniu łódki i urządzenia łódka ma za zadanie przepłynąć do celu w linii prostej. I tyle nie chce żadnych fajerwerków sterowania itd. tylko sam algorytm wyznaczenia i utrzymywania kierunku przez łódkę tak by do celu płynęła w linii prostej Wymagane źródła najlepiej w C (ale nie upieram się ), procesor STM32F1xx lub STM32F4xx do tego GPS RadioLink M8N SE100 i jakiś IMU jest mi obojętne może być GY-91 lub inne wskazane. Oferty tylko poważne, proszę kierować na mail nawasaqi@o2.pl Budżet 1500zł.
  12. Mam dwie tablice char: char t1[100]; char t2[2048]; oraz wskaźnik który zależnie od potrzeb wskazuje na t1 lub t2 char * ptr; ptr = t1; czy jest możliwość aby za pomocą sizeof pobrać rozmiar tablicy na którą aktualnie ustawiony jest wskaźnik?
  13. Pisząc drobne programiki na AVR w C nie był możliwy taki konstrukt: int w = 10; // przykład, w może oznaczać rozmiar ciągu danych np: z uart char str[w]; Tymczasem działając teraz z programem pod esp8266 tak z ciekawości napisałem: int packetSize = Udp.parsePacket(); char packetBuffer[packetSize]; Pytanie teraz do ekspertów: czy powyższy zapis jest prawidłowy? to się kompiluje i działa ale czy nie powinienem zastosować jakichś funkcji do dynamicznej alokacji danych?
  14. O ile wydrukowanie stringu z flasch jest dla mnie oczywiste proste i zrozumiałe Serial.print(F("test")); to nie bardzo wiem jak tego makra(PGMP) użyć w funkcjach z końcówką _P np jak zapisać wersję tego: strcmp("Audyt N. 7 ",s) Tak aby string "Audyt N. 7 " był pobierany bezpośrednio z flasch? np: gdy próbuję zapisać (to jest cały warunek) if((lineMem == 0) && (0 == strcmp_P("Audyt N. 7 ",s))) Kompilacja przebiega prawidłowo jednak zajętość RAM nie zmienia się, a chyba powinna być pomniejszona o ilość znaków napisu Audyt..... ?
  15. CUDA na kiju (a nawet GPU) cz. 1 Wstęp Tym artykułem chciałbym zapoczątkować cykl artykułów na temat programowania równoległego z wykorzystaniem kart graficznych (GPU) w języku CUDA C. W części pierwszej przedstawię krótki wstęp teoretyczny tzn. omówię dlaczego równoległe wykonywanie obliczeń może istotnie przyspieszyć działanie programu, opiszę budowę procesorów graficznych, a w dalszych częściach będą przedstawione praktyczne wskazówki z fragmentami kodu programu do mnożenia macierzy. Zapraszam do lektury! Treść artykułu została usunięta, ponieważ użytkownik publikujący ten poradnik wykorzystał w nim materiały, do których nie posiadał odpowiednich praw. Usunięcie nastąpiło na prośbę autora zapożyczonych materiałów.
  16. Czołem, jako osoba mniej ścisła, męczę się od wczoraj z próbą ustawienia dwóch zmiennych pobranych z pliku txt zlokalizowanym na zdalnym serwie, korzystając z biblioteki HTTPClient.h. Plik tekstowy ma zawartość "640, 1530" (ale może być też dowolny separator lub nawet w osobnej linii, mam dostęp do tego pliku i mogę go zrobić jak chcę, wazne aby działało). http.begin("http://192.168.1.2/zmienne.txt"); int httpCode2 = http.GET(); if (httpCode2 > 0) { //Check for the returning code String payload2 = http.getString(); } else { Serial.println("<br>Blad pobrania danych z serwa</br>"); } http.end(); Za cholerę nie potrafię przerobić tych dwóch wartości na 2 osobne zmienne liczbowe czyli np.: zmienna1 = 640; zmienna2 = 1530; Kombinowałem i przerobieniem tego na tablicę ale wówczas zmienna[0] = 640 a zmienna[1] = 0. Próbowałem używać funkcji konwertujacych string na int, ale rezultaty podobne. Większość przykładów na necie to operowanie pobieranym plikiem z np. karty SD i użycie funkcji fscanf, ale przypadku pobrania zawartości pliku przez http.GET() to już nie działa. Myślałem też o przerobieniu tego na XML, ale to bez sensu skoro to tylko 2 liczbowe wartości. Będę wdzięczny za naprowadzenie. Pozdro
  17. Z konieczności utworzenia małego mobilnego systemu pomiarowego, pojawiła się potrzeba połączenia mikrokontrolera z telefonem pracującym na Androidzie, tak aby ten drugi otrzymywał dane z pierwszego. Wybór podstawowej platformy (Android) padł z uwagi na jej powszechność, a także brak konieczności zakupu płytek, czujników itp. Dlatego też chciałbym ośmielić tym artykułem osoby, które noszą się z zamiarem tworzenia projektów, które wymagają dużej ilości dostępnych w telefonach czujników, interfejsów komunikacyjnych i możliwości obliczeniowych, które czasem się marnują, a samo przedsięwzięcie nie przewiduje trwałego montażu telefonu w projektowanym urządzeniu (choć pewnie dużo osób i tak ponownie wykorzystuje te same mikrokontrolery/płytki z czujnikami itd. do kolejnych projektów). Jedną z wad takiego rozwiązania, jest brak zestawu pinów ogólnego przeznaczenia (GPIO) w telefonach. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » W kilku słowach niniejszy poradnik przeprowadzi przez proces tworzenia aplikacji na Androida (Java), która będzie komunikować się poprzez wirtualny port szeregowy za pomocą przewodu USB z całkiem nowym Raspberry Pi Pico. Oczywiście, omówiony zostanie również kod w języku C, który będzie sterował stanami logicznymi pinów mikrokontrolera (włączony/wyłączony) na podstawie odczytu wysłanych z telefonu danych. Ponadto będzie on również na żądanie telefonu odczytywał stan logiczny wybranych wejść i wysyłał te informacja, które wyświetlone zostaną w aplikacji mobilnej. Dodatkowo zaznajomi on z przygotowaniem środowiska do kompilacji programów w C/C++ na Raspberry Pi Pico jak i podpowie jak do pewnego stopnia zautomatyzować proces kompilacji i wgrywania programu do mikrokontrolera. Przygotowanie aplikacji na telefon z Androidem Po pierwsze należy pobrać i zainstalować zintegrowane środowisko programistyczne (IDE) Android Studio. Po utworzeniu nowego projektu z domyślną opcją "Empty Activity" i następnie wybranym "API21: Android 5.0 (Lollipop)" w pozycji "Minimum SKD" można zacząć od utworzenia pliku o nazwie "usb_devices.xml" o następującej zawartości (widoczne w pliku wartości wyjaśnione zostaną później): <?xml version="1.0" encoding="utf-8"?> <resources> <usb-device product-id="10" vendor-id="11914" /> </resources> Należy skopiować go do katalogu "app\res\xml" tak jak na zrzucie poniżej. Albo skopiować plik w Eksploratorze Windows i wkleić w Android Studio (może być wymagane utworzenie katalogu "xml" - patrz opcje menu kontekstowego), albo utworzyć bezpośrednio w edytorze Android Studio, czy znaleźć jego lokalizację na dysku (domyślnie "Litera:\Users\Użytkownik\AndroidStudioProjects\NazwaProjektu\app\src\main\res\xml"). Następnie można przystąpić do konfiguracji pliku "AndroidManifest.xml" (do znalezienia jak na zdjęciu powyżej w katalogu "manifests"). Należy wkleić poniższe linijki kodu: <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device" /> tak jak pokazano to na zrzucie ekranu: Aplikacja będzie korzystała z zewnętrznej biblioteki usb-serial-for-android, którą należy podłączyć do aplikacji. Zrobić można to edytując najpierw plik "build.gradle (Project)", następnie - "build.gradle (Module)" tak, jak przedstawiono na zdjęciach poniżej: wklejając poniższe linijki: maven { url 'https://jitpack.io' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } implementation 'com.github.mik3y:usb-serial-for-android:3.3.0' Teraz można zsynchronizować projekt klikając na "Synch Now" (ostatni rysunek powyżej). Teraz należy wkleić poniższy kod do pliku "app\res\layout\activity_main.xml". Będzie opisywał on wygląd ekranu aplikacji - pole tekstowe i dwa przyciski. <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tekst" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="vendorID productID" android:textSize="29sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/on" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="50dp" android:layout_marginTop="50dp" android:text="ON" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/off" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:layout_marginEnd="50dp" android:text="OFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> Teraz przyszła pora na kod programu. Należy otworzyć plik "MainActivity.java" z katalogu "app\java" i zastąpić jego zawartość (poza pierwszą linijką rozpoczynającą się od "package...") tą podaną poniżej. Komentarz zawarte w kodzie opisują "co jest od czego" i ich przestudiowanie da pogląd na to, jak to wszystko działa. // Zaimportowanie używanych przez aplikację klas import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; // Zaimportowanie zewnętrzenej biblioteki "usb-serial-for-android" import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; import com.hoho.android.usbserial.driver.ProbeTable; import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.util.SerialInputOutputManager; public class MainActivity extends AppCompatActivity { // Deklaracja zmiennych globalnych - opis w dalszej części kodu TextView poleTekstowe; BroadcastReceiver broadcastReceiver; UsbSerialPort usbSerialPort; SerialInputOutputManager serialInputOutputManager; ScheduledFuture co100Ms; ExecutorService rx; Button on; Button off; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Załadowanie pliku układu okna i elementów aplikacji setContentView(R.layout.activity_main); // Ekran będzie włączony tak długo, jak aplikacja będzie widoczna na głównym planie getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Przypisanie pola tekstowego z pliku układu do zmiennej w kodzie aplikacji poleTekstowe = findViewById(R.id.tekst); // Definicja obiektu, który będzie nasłuchiwał zdarzeń związanych z podłączeniem i odłączeniem // miktokontrolera do telefonu broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Przełącznik "switch" - program nie musi sprawdzać po kolei wszystkich "if'ów" czy // "else if'ów", tylko kieruje się do właściwego miejsca za pierwszym podejściem switch (intent.getAction()) { // Obsługa akcji podłączenia urządzeni USB do telefonu case "android.hardware.usb.action.USB_DEVICE_ATTACHED": UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); // Wartości, które należy umieścić we wspomnianym wcześniej pliku "usb_device.xml" poleTekstowe.setText(usbDevice.getVendorId() + " " + usbDevice.getProductId()); ProbeTable probeTable = new ProbeTable(); // Przypisanie odpowiedniego dla Pico sterownika probeTable.addProduct(usbDevice.getVendorId(), usbDevice.getProductId(), CdcAcmSerialDriver.class); UsbSerialProber usbSerialProber = new UsbSerialProber(probeTable); UsbSerialDriver usbSerialDriver = usbSerialProber.probeDevice(usbDevice); UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); // Przypisanie wcześniej zdefiniowanego sterownika do wirtualnego portu // szeregowego USB usbSerialPort = usbSerialDriver.getPorts().get(0); try { // Próba otwarcia portu szeregowego dla mikrokontrolera usbSerialPort.open(usbDeviceConnection); // Zdefiniowanie parametrów komunikacji usbSerialPort.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // Sygnał Data Terminal Ready - Pico i Android rozpocznął komunikację usbSerialPort.setDTR(true); // Sygnał Request To Send - wymaga go np. Arduino do rozpoczęcia komunikacji z Androidem usbSerialPort.setRTS(true); } catch (Exception ignored) {} // Obiekt nasłuchujący danych przychodzących SerialInputOutputManager.Listener serialInputOutputListener = new SerialInputOutputManager.Listener() { @Override public void onRunError(Exception ignored) {} @Override public void onNewData(byte[] data) { runOnUiThread(() -> poleTekstowe.setText(new String(data))); } }; serialInputOutputManager = new SerialInputOutputManager(usbSerialPort, serialInputOutputListener); serialInputOutputManager.setReadTimeout(0); // Definicja pozyższego obiektu jako oddzielnego wątku programu... rx = Executors.newSingleThreadExecutor(); // ...i jego uruchomienie rx.submit(serialInputOutputManager); // Zdefiniowanie osobnego wątku, który będzie wywoływał się do 100 ms wysyłając // porcję danych co100Ms = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { try { usbSerialPort.write("o".getBytes(), 0); } catch (Exception ignored) {} }, 0, 100, TimeUnit.MILLISECONDS); break; // Obsługa akcji podłączenia urządzeni USB do telefonu case "android.hardware.usb.action.USB_DEVICE_DETACHED": // Sprzątanie po ustanowionej wcześniej komunikacji do odłączeniu Pico od Androida if (co100Ms != null && rx != null) { co100Ms.cancel(false); serialInputOutputManager.stop(); rx.shutdown(); poleTekstowe.setText("Odłączono"); } break; } } }; // Definicja filtrów podłączone/odłączone urządzenie USB IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); // Uruchomienie nasłuchiwania podłączenia/odłączenia urządzenia USB this.registerReceiver(broadcastReceiver, intentFilter); // Definicja przycisków "ON" i "OFF" on = findViewById(R.id.on); on.setOnClickListener(v -> { try { // Wysłanie ciągu znaków do mikrokontrolera usbSerialPort.write("n".getBytes(), 0); } catch (Exception ignored) {} }); off = findViewById(R.id.off); off.setOnClickListener(v -> { try { usbSerialPort.write("f".getBytes(), 0); } catch (Exception ignored) {} }); } // Sprzątanie po ustanowionej komunikacji w momencie zamknięcia (strzałką wstecz) aplikacji @Override protected void onDestroy() { if (co100Ms != null && rx != null) { co100Ms.cancel(false); serialInputOutputManager.stop(); rx.shutdown(); } this.unregisterReceiver(broadcastReceiver); super.onDestroy(); } } Ostatnim krokiem w tej części będzie skompilowanie pliku *.apk aplikacji, który to będzie można zainstalować w telefonie. Wybieramy z menu "Build" pozycję "Generate Signed APK...", wybrać "APK", utworzyć nowy klucz ("Create new..."), wybrać opcję "Build Variants" "release" oraz "Signature Versions" "V2..." i zakończyć kreatora przyciskiem "Finish" - zrzuty poniżej. W powiadomieniu wyświetli się lokalizacja do pliku *.apk. Za jego pomocą można zainstalować aplikację w telefonie. Gotowe! Przynajmniej androidowa aplikacja 😉 Pliki do znalezienia tutaj: Android.zip Program dla Raspberry Pi Pico Za wszelką cenę chciałem uruchomić Pico SDK, aby móc zaprogramować "Małą Malinkę" w C i finalnie kopiując plik *.uf2 do widocznej pamięci masowej po podłączeniu jej do komputera trzymając przyciśnięty przycisk "BOOTSEL". Było to zanim powstał skrypt dla Windowsa, a że na Windowsie 7 nie chciał współpracować żaden instalator Microsoftu, to rozwiązałem to w inny sposób. Jak to mówią "the hard way". Po pierwsze nigdy nie zrobiłem nic w C, co więcej oficjalna instrukcja do C/C++ mówiła np. o łatwej konfiguracji chociażby CLion'a. Nie wspominała tylko, że na Linuksie... A więc do rzeczy. Pobieramy następujące składniki: ARM GCC Compiler CMake MinGW Python 3 Raspberry Pi Pico SDK TinyUSB Następnie wypakowujemy je w swojej ulubionej lokalizacji (ja utworzyłem oddzielny folder, gdzie przechowuję binarki programów do "devovania"). Nie ma instalatorów, więc otwarcie mogę się przyznać, że mam niechęć do nich, jak ja coś zrobię, to wiem, gdzie naśmieciłem i łatwiej później posprzątać. Można się przyznawać się, kto też tak ma 🤷‍♂️ Co zrobić... A nie, jest MinGW, instalujemy architekturę x86_64. Pierwsze trzy pozycje będą zawierały podkatalog "bin", których ścieżki należy umieścić w systemowej zmiennej "PATH" (Komputer>Właściwości>Zaawansowane ustawienia systemu>Zmienne środowiskowe>Zmienne systemowe) oddzielając je średnikiem. Python 3, tutaj należy podać ścieżkę katalogu głównego. Zawartość TinyUSB kopiujemy do podkatalogu "lib" folderu gdzie wypakowano Raspberry Pi Pico SDK. Można się też pokusić o utworzenie zmiennej użytkownika "PICO_SDK_PATH" zawierającej ścieżkę do wspomniajego w poprzednim zdaniu katalogu. Na zdjęciu tak to wygląda: Pora przygotować właściwe pliki. Najpierw "CMakeLists.txt", który będzie miał zawartość jak poniżej. Należy wspomnieć, że plik"pico_sdk_import.cmake" należy umieścić w katalogu projektu, a znaleźć można go w katalogu zawierającym Raspberry Pi Pico SDK w podfolderze "external". cmake_minimum_required(VERSION 3.17) include(pico_sdk_import.cmake) project(project_name) # nazwa projektu set(CMAKE_C_STANDARD 11) pico_sdk_init() add_executable(project_name file_name.c) # dodanie plików wykonywalnych projektu target_link_libraries(project_name pico_stdlib) pico_enable_stdio_usb(project_name 1) # włączenie połączenia szeregowego USB pico_enable_stdio_uart(project_name 0) # wyłączenie UART pico_add_extra_outputs(project_name) Teraz tworzymy długo wyczekiwany plik *.c taki jak wskazany w "CMakeLists.txt" plik wykonywalny. Jego zawartość będzie jak poniżej. Komentarze rozjaśnią sprawę. #include <stdio.h> #include "pico/stdlib.h" int main() { stdio_init_all(); // Użycie pinu GP25 (dioda LED na płytce) gpio_init(25); // Ustawienie GP25 jako wyjścia gpio_set_dir(25, GPIO_OUT); gpio_init(0); // Ustawienie GP0 jako wejścia gpio_set_dir(0, GPIO_IN); gpio_init(1); gpio_set_dir(1, GPIO_IN); // Podciąga GP1 poprzez wbudowany w czip RP2040 opornik - stan wysoki (1), gdy nie podłączony do masy (GND) gpio_pull_up(1); // Bufor dwóch znaków. Efektywnie jednego, drugi to znak terminalny "\0" // Jeśli chcesz odczytać 5 znaków, ustaw należy ustawić bufor na 6 char wiadomosc[2]; while (true) { // Odczytuje dwa znaki (łącznie z terminalnym) w wejścia. Jeśli chcesz odczytywać wiadomości // ze zmienną ilością znaków, użyj "gets(wiadomosc)" zamiast "fgets(wiadomosc, 2, stdin)" // i zakończ wysyłane wiadomości znakiem kowej linii - "\n" fgets(wiadomosc, 2, stdin); // Odczytuje pierwszy znak (indeks [0]) wektora zawierającego wiadomość. // Java może porównać cały ciąg w instrukcji "switch" ;P switch(wiadomosc[0]) { case 'n': // Włącza diodę led przy wiadomości "n" ze standardowego wejścia gpio_put(25, 1); break; case 'f': // Wyłącza diodę led przy wiadomości "f" ze standardowego wejścia gpio_put(25, 0); break; case 'o': // Odczytuje wartości stanów GP0 i GP1 - wysoki (1) lub niski (0) // i wysyła je przez połączenie szeregowe printf("GP0: %d, GP1: %d", gpio_get(0), gpio_get(1)); break; } } } Do kompilacji przygotujemy plik wsadowy *.bat, który ułatwi zautomatyzuje kompilację. Jego zawartość będzie następująca: @echo off mode con: cols=130 lines=32 for %%i in (%1) do (set sciezka=%%~pi) for %%i in (%1) do (set litera=%%~di) cd /d "%litera%%sciezka%" cmake.exe -DCMAKE_BUILD_TYPE=Release -G "CodeBlocks - MinGW Makefiles" "%litera%%sciezka%" cmake.exe --build "%litera%%sciezka%" robocopy "%litera%%sciezka% " "G:\ " "*.uf2" /nfl /ndl /njh /njs /np pause Należy zmienić literę "G:\ " na tą, pod którą pojawi się dysk wymienny Pico. Spacje nie są błędem. Dziwna sprawa, ale inaczej nie chce działać, przynajmniej na Siódemce, a dodanie spacji na końcu było rozwiązaniem. Przygotowany plik chowamy przed wzrokiem i np. programem Default Programs Editor tworzymy pozycję w menu kontekstowym pliku *.c a jeszcze lepiej tworzymy nowe rozszerzenie dla plików C Pico. Albo kompilujemy wykorzystując dwie linijki zaczynające się od "cmake.exe", albo korzystamy ze szpanerskiego menu, a efekt kompilacji będzie następujący: Pliki do znalezienia tutaj: Pico.zip Pierwsze uruchomienie Po uruchomieniu aplikacji na telefonie i podłączeniu Pico przez kabel USB i adapter OTG należy zgodzić się, aby aplikacja uruchamiała się wraz z podłączeniem mikrokontrolera do smartfona. Nada to uprawnienia do korzystania z połączenia USB aplikacji. Pola "vendorID" i "productID" zmienią swoje wartości na te, które trzeba było umieścić we wspomnianym wcześniej pliku "usb_devices.xml". Po odłączeniu i ponownym podłączeniu (w trakcie działania aplikacji mobilnej, gdyż w androidowej aplikacji odczyt od cykliczne nadawanie zakodowane jest po podłączeniu urządzenia, ale tym zdefiniowanym w pliku "MainActivity.java", który wykonuje się po jej uruchomieniu) Malinki będzie można włączyć i wyłączyć jej diodę LED (GP25) i co 100 ms (na żądanie telefonu) będzie odczytywany stan wejść GP0 i GP1. Jak to działa, na zdjęciach poniżej. Zwarcie pinów GP0 (1) i 3V3OUT (36) ustawi stan wysoki na wejściu GP0, a połączenie GP1 (2) z GND (3) przesteruje normalnie wysoki stan do niskiego. Gotowe pliki wykonywalne tutaj: Binarki.zip Podsumowanie Niniejszy artykuł nie jest projektem od A do Z jakiegoś przedsięwzięcia, tylko przedstawia zagadnienie komunikacji portem szeregowym pomiędzy mikrokontrolerem (tutaj Raspberry Pi Pico, choć nie musi to być on) a telefonem komórkowym z Androidem wykorzystując natywne rozwiązania tworzenia programów na te platformy, tym samym nie będąc na "łasce" np. rozwiązań modułowych tworzenia aplikacji z klocków, wykorzystując wszystkie możliwości jakie daje Android, Pico, Java i C. Oczywiście to tylko zalążek, ale nie znalawszy gotowego rozwiązania, zmotywowało mnie to, żeby takie zrobić i zaprezentować, ktoś może mieć łatwiej. Ja już mam łatwiej, bo takiego potrzebuję 😁 PS. Nie spodziewałem się, że tak długo zajmie mi napisanie tego tekstu... PS2. Nazwa aplikacji mobilnej to taka losowo wklepana na klawiaturze. PS3. Pierwszy obrazek, drugi kod i trzy następne obrazki to trochę ludzik, trochę android 🤖
  18. CUDA na kiju (a nawet GPU) cz 2. Wstęp W poprzedniej części omówiliśmy sobie dlaczego warto wykorzystać procesory graficzne do obliczeń oraz po krótce zapoznaliśmy się z ich budową. W tej części omówimy sobie wszystko to co należy zrobić przed napisaniem programu: podam kilka informacji przydatnych podczas rozważania programu równoległego, przygotujemy środowisko do pisania programu w CUDA C, a następnie przygotujemy podwaliny pod pierwszy program tzn. opracujemy sobie teorię mnożenia macierzy oraz napiszemy prototyp programu na CPU. Gotowi? No to jazda!!! Treść artykułu została usunięta, ponieważ użytkownik publikujący ten poradnik wykorzystał w nim materiały, do których nie posiadał odpowiednich praw. Usunięcie nastąpiło na prośbę autora zapożyczonych materiałów.
  19. Cześć, szukam osób, których pasją jest programowanie mikrokontrolerów i które chciałyby zająć się tym tematem na poważnie, lub już się tym zawodowo zajmują. Wymagana podstawowa znajomość języka C lub C++. Oferuję pomoc w przygotowaniu do wymaganego stanowiska. Wymagana jest również znajomość języka angielskiego na poziomie komunikatywnym. Miejsce pracy: Gliwice. Branża: motoryzacyjna. Zainteresowanych proszę o wiadomość prywatną.
  20. Dość długo zabierałem się za ten projekt, ale w końcu udało się go zrealizować. Powstał on, ponieważ potrzebowałem czegoś, co umożliwi mi szybki wgląd w aktualne obciążenie komputera, bez niepotrzebnego przełączania okienek. Rozwiązanie czysto programowe byłoby też w moim przypadku o tyle mniej praktyczne, że często korzystam z maszyn wirtualnych, a taki zewnętrzny monitor obciążenia pozwala mi na wyświetlanie statystyk wszystkiego jednocześnie. Poza wyświetlaniem informacji zależało mi także na graficznej reprezentacji obciążenia, dzięki czemu można nawet "kątem oka" zauważyć że pojawiło się jakieś duże obciążenie lub że kończy się pamięć RAM. Parę lat temu zbudowałem taki monitor na ekranie LCD 4x20, jednak z biegiem czasu i modernizacją sprzętu coraz ciężej było zmieścić na nim wszystkie informacje które bym chciał, dlatego stwierdziłem że czas na nową odsłonę 😄 Tym razem zamiast ekranu LCD postanowiłem użyć wyświetlaczy OLED. Z kolei do reprezentacji graficznej chciałem wykorzystać diody WS2812. Dość długo zastanawiałem się w jaki sposób je ułożyć i jak to najprościej zrobić. Ostatecznie diody zostały ułożone w dwóch półpierścieniach nad wyświetlaczami. Aby ułatwić sobie życie kupiłem pierścienie 16 diodowe i 24 diodowe i przeciąłem na na pół. Wadą takiego rozwiązania było to, że tylko na jednej połówce miałem do dyspozycji wygodne pola lutownicze z tyłu, a na drugiej musiałem wlutować kable bezpośrednio w pierwszą diodę. Na zdjęciu widać pierwsze przymiarki diod do wyświetlacza: Docelowo planowałem wykonać 4 takie zestawy oled + diody, co wiązało się z pewnym problemem. Wyświetlacze które posiadałem podłącza się przez I2C, a ponieważ miałem 4 identyczne, to wszystkie miały ten sam adres, niestety producent wyświetlaczy nie przewidział takiego scenariusza (niektóre wersje umożliwiają ustawienie ostatniego bitu, co umożliwia podpięcie 2 wyświetlaczy, moje akurat nie miały takiej opcji). Po zrobieniu researchu rozwiązanie tego problemu okazało się bardzo proste. Polega ono na selektywnym doprowadzaniu sygnału zegara do poszczególnych wyświetlaczy. Ja w tym celu wykorzystałem demultiplekser 74HC139. Takie rozwiązanie jest możliwe, ponieważ tylko master (w tym wypadku mikrokontroler) steruje linią zegara, dzięki temu możliwe jest zastosowanie demultipleksera. W przypadku gdyby slave'y też sterowały linią zegara rozwiązanie byłoby bardziej złożone (o ile w ogóle możliwe), bo demultipleksery działają tylko w jedną stronę 😄 Z tego powodu też nie można tego zrealizować na linii danych (nawet jeśli przesyłanie danych odbywa się tylko w jedną stronę, to linia SDA musi być "dwustronna" żeby możliwa była realizacja protokołu I2C). W przypadku diod WS2812 sprawa była prostsza, docelowo każdy półpierścień dostał własny pin procesora, dzięki czemu każdy z nich może być odświeżany niezależnie. Jako mikrokontroler wybrałem Atmegę4809, ponieważ potrzebowałem 4kB ramu na trzymanie klatek z każdego z 4 oledów (128*64*4 = 32kb = 4kB). Możliwa by była realizacja przy mniejszej ilości pamięci, np 2kB, poprzez trzymanie tylko jednej klatki, jednak wiązałoby się to z mniejszą wydajnością całego programu, ponieważ generowanie kolejnej klatki można by było rozpocząć dopiero po zakończeniu przesyłania poprzedniej. W przypadku gdy każdy wyświetlacz ma swój bufor, to w trakcie gdy na przykład dane są wysyłane do pierwszego wyświetlacza możliwe jest generowanie obrazu dla drugiego wyświetlacza itd. Wymaga to asynchronicznej implementacji I2C, co zrealizowałem poprzez kolejkę do której można dodawać wskaźniki na tablice danych które mają być po kolei wysłane i ich obsługa jest w pełni realizowana na poziomie przerwania. Samo generowanie klatek zrealizowane jest w ten sposób, że dla każdego wyświetlacza przygotowany jest szablon zawierający stałe części obrazu, a następnie renderowane są na nim wartości w określonych pozycjach x,y, co łącznie generuje pełną klatkę obrazu. Graficznie wygląda to tak (po lewej templatka, po prawej z wypełnionymi wartościami): Wszystkie przygotowane ekrany wyglądają następująco: Ekrany przygotowane są stricte pod moją konfigurację, oczywiście możliwe by było generowanie wszystkiego po stronie komputera i przesyłanie gotowych klatek, akurat ja wolałem podejście z renderowaniem po stronie uC, ale co kto lubi 😄 Układ został zrealizowany na płytce uniwersalnej, a szkielet i obudowa zostały wydrukowane na drukarce 3D. Projekt ma budowę modułową i działa poprawnie z każdą liczbą modułów między 1 a 4, na ten moment mam zrealizowane dwa, kolejne dwa czekają aż będę miał trochę więcej wolnego czasu. Ponieważ każdy moduł wykorzystuje 4 kable do przesyłania danych (2 do pierścieni i 2 do wyświetlacza), to do ich podłączania wykorzystałem skrętkę, akurat po pół skrętki na moduł. Na ten moment całość prezentuje się następująco, kable z boku są przygotowane dla następnych modułów. Urządzenie łączy się z komputerem poprzez konwerter USB↔UART. Po stronie komputera działa aplikacja napisana w javascripcie, która zbiera dane z komputera na którym działa, używając głównie biblioteki systeminformation. Dodatkowo, nasłuchuje na jednym z portów sieciowych, pod który mogą się podpinać maszyny wirtualne i przesyłać dane o swoim obciążeniu. Technicznie, zamiast maszyn wirtualnych możliwe by było podpinanie innych fizycznych urządzeń, opcji jest wiele, wszystko zależy od potrzeb. Wyświetlanie informacji z dwóch źródeł jednocześnie na pierścieniach realizowane jest poprzez kolory, host wykorzystuje zielony i czerwony, natomiast maszyna wirtualna niebieski.
  21. Założeniem projektu było zbudowanie w krótkim czasie biurkowego zegara. Na początku rozważałem zbudowanie prostego zegara na wyświetlaczach siedmiosegmentowych, jednak po przeanalizowaniu potrzebnych funkcjonalności stanęło na cyfrowym wyświetlaczu LCD. Taki wyświetlacz, dzięki bardzo małemu poborowi prądu, umożliwił sensowne zasilanie bateriami. Był to mój pierwszy projekt zasilany w ten sposób, gdzie czas pracy miał być w założeniu liczony przynajmniej w miesiącach. Wyświetlacz który użyłem w projekcie to DE 120-RS-20/7,5/V, który charakteryzuje się pełnymi 4 cyframi o wysokości 18mm i, co ważne, nie jest multipleksowany, dzięki czemu nieco łatwiej było napisać program który nim sterował. Jeśli chodzi o mikrokontroler, to wybrałem Atmegę 4809, ponieważ był to jedyny mikrokontroler z nowej rodziny AVR, dostępny w wersji przewlekanej, posiadający odpowiednią liczbę i/o. W kwestii zasilania stanęło na dwóch bateriach AA, zastosowanie AAA nie miałoby większego wpływu na rozmiar całości, z kolei zastosowanie baterii typu CR2032 znacząco by zmniejszyło czas pracy. Całość została zlutowana na płytce uniwersalnej, połączenia zostały zaprojektowane w eagle. Było kilka iteracji projektu, tak aby zminimalizować liczbę zworek i maksymalnie uprościć połączenia. Atmega została umieszczona pod ekranem, natomiast z drugiej strony został przykręcony zasobnik na baterie. Do ustawiania czasu służą dwa przyciski - jeden ustawia godziny, drugi minuty. Wydaje mi się to najprostszą możliwą metodą ustawiania czasu (poza opcją automatyczną :)). Piny w mikroprocesorze wykorzystane są wszystkie, przy czym nie wszystkie segmenty wyświetlacza są podłączone - ucierpiały kropki i jeden segment z pierwszej cyfry, który w przypadku wyświetlania tylko i wyłącznie czasu jest niepotrzebny, przy założeniu że godziny poranne wyświetlamy bez zera, czyli np 8:36 a nie 08:36. Jedną z ciekawszych części projektu była realizacja możliwie energooszczędnego oprogramowania. Wyświetlacze, takie jak ten zastosowany w projekcie, wymagają odświeżania z częstotliwością 30Hz - 100Hz (rekomendowane jest 32Hz). Jest kilka możliwości energooszczędnej realizacji tego zadania, ja ostatecznie zdecydowałem się na napędzanie Atmegi wewnętrznym oscylatorem z dzielnikiem 10, co dawało ok 2MHz i całkowite usypianie procesora na czas bezczynności (power down). Odmierzanie czasu, a jednocześnie wybudzaniem procesora z trybu power-down realizowane było natomiast z użyciem kwarcu zegarkowego i okresowego przerwania z modułu RTC, wywoływanego co 512 cykli kwarcu (co daje 64Hz). Zasada działania całości jest raczej prosta - co 1/64s wywoływane było przerwanie, program doliczał sobie kwant czasu, obsługiwał wyświetlacz (albo odwracał wszystkie piny, albo "renderował" nowy czas) i szedł dalej spać. W celu dodatkowej optymalizacji, wszystkie możliwe godziny zostały stablicowane przy pomocy małego skryptu w Typescripcie. Dzięki temu wyświetlenie nowej godziny było bardzo szybkie - wystarczyło odczytać 5 kolejnych wartości z tablicy (po jednej na każdy port uC) i wpisać je na odpowiednie porty (po uprzednim przepisaniu wartości z pinów które nie były podłączone do wyświetlacza). Takie podejście zapewniło mi pobór prądu, który był ciężki do zmierzenia moim tanim miernikiem, wynosił średnio ok 5-6uA, co przy idealnych warunkach daje jakieś 50 lat pracy na baterii (nie uwzględniając samorozładowania). Wiadomo, taka wartość jest nieosiągalna, ale pozwala przypuszczać, że przynajmniej kilka lat zegar powinien podziałać na jednym komplecie baterii. Obudowa zegara została wydrukowana na drukarce 3D. Śrubki, które trzymają całość w jednym kawałku, nie są może najładniejsze, ale mogłem wykorzystać tylko to co miałem po ręką 😄. Gwinty zostały przylutowane do płytki uniwersalnej (są to po prostu konektory typu zacisk śrubowy, wykorzystane w trochę innym celu). W trakcie budowy sporym problemem okazały się zakłócenia powstające na ścieżkach. Było to do przewidzenia, jednak nie spodziewałem się że tak mocno wpłynie na stabilność kwarcu. Ostatecznie musiałem nieco przeprojektować płytkę i pozmieniać część ścieżek, tak aby zmaksymalizować wolną przestrzeń dookoła kwarcu, zmieniłem też nieco wartości kondensatorów przy nim. Myślę że w przypadku wykonania normalnej PCB takie problemy by nie wystąpiły, możliwe by też było otoczenie kwarcu polem masy. Jeśli chodzi o możliwości rozbudowy, to możliwe by było dodanie wyświetlania temperatury, ponieważ Atmega4809 ma wbudowany termometr. Z kolei przejście na SMD i zmiana zasilania np na baterie CR2032 pozwoliłaby na zauważalne zmniejszenie grubości zegara.
  22. Cześć, Jako że to mój pierwszy post tutaj nie bijcie 😄 Mam zerowe doświadczenie w C i małe w innych językach więc pewnie to czego szukam jest banalne... Do rzeczy. Robię swój pierwszy projekt na AtMega88PA, którego częścią jest wyświetlacz 2x16 i zegar czasu rzeczywistego DS1307. Oba urządzenia podłączone są przez i2c do procka (wyświetlacz poprzez extender) i wyświetlacz udało mi się zmusić do pracy - czyli komunikacja i2c działa. Schemat podłączenia w załączniku. Połączenie sprawdzone. Bateria po odłączeniu programatora daje 1 mikroamper do układu więc zakładam że zegar podtrzymuje swoje działanie. Po ustawieniu zegara jakiś czas temu (zanim zaprogramowałem wyświetlacz) udało mi się wyprowadzić sekundnik na diodę - time.sec%2 powodowało zmianę stanu LED'a i ten ładnie mrygał w odstępie sekundowym. Zegar ustawilem nastepujacym kodem: RTC_Init(); rtc_t rtc; rtc.hour = 0x10; // 10:40:20 am rtc.min = 0x40; rtc.sec = 0x00; rtc.date = 0x01; //1st Jan 2016 rtc.month = 0x01; rtc.year = 0x16; rtc.weekDay = 5; // Friday: 5th day of week considering monday as first day. RTC_SetDateTime(&rtc); // 10:40:20 am, 1st Jan 2016 Do projektu dodałem parę urządzeń, zaprogramowałem wyświetlacz. Schody zaczęły się gdy próbuję wyprowadzić stan zegara na ekran. Pojawia się takie coś: https://youtu.be/qWDivu2OPvA main.c : int main(void) { // tutaj inicjacja wyswietlacza, we/wy - nieistotne rtc_t time; lcd_led(0); char i; RTC_Init(); while(1){ RTC_GetDateTime(&time); // metoda do poboru danych z zegara lcd_gotoxy(0,0); // ustaw kursor na punkt 0 w zegarze i = (char)time.sec; // mapowanie zmiennej sekundowej do znaku - lcd_puts(&i); // metoda wyswietlajaca znaki na wyswietlaczu } } Clock.c : #include "twi.h" #include <avr/io.h> #include <util/delay.h> #define Clock_Read 0xD1 // bit odczytu #define Clock_Write 0xD0 // bit zapisu typedef struct { uint8_t sec; uint8_t min; uint8_t hour; uint8_t weekDay; uint8_t date; uint8_t month; uint8_t year; }rtc_t; void RTC_Init(void) { twi_init(); // Initialize the I2c module. twi_start(); // Start I2C communication twi_write(Clock_Write); // Connect to DS1307 by sending its ID on I2c Bus twi_write(0x07); // Select the Ds1307 ControlRegister to configure Ds1307 twi_write(0x00); // Write 0x00 to Control register to disable SQW-Out twi_stop(); // Stop I2C communication after initializing DS1307 } void RTC_SetDateTime(rtc_t *rtc) { twi_start(); // Start I2C communication twi_write(Clock_Write); // connect to DS1307 by sending its ID on I2c Bus twi_write(0x00); // Request sec RAM address at 00H twi_write(rtc->sec); // Write sec from RAM address 00H twi_write(rtc->min); // Write min from RAM address 01H twi_write(rtc->hour); // Write hour from RAM address 02H twi_write(rtc->weekDay); // Write weekDay on RAM address 03H twi_write(rtc->date); // Write date on RAM address 04H twi_write(rtc->month); // Write month on RAM address 05H twi_write(rtc->year); // Write year on RAM address 06h twi_stop(); // Stop I2C communication after Setting the Date } void RTC_GetDateTime(rtc_t *rtc) { twi_start(); // Start I2C communication twi_write(Clock_Read); // connect to DS1307 by sending its ID on I2c Bus twi_write(0x00); // Request Sec RAM address at 00H twi_stop(); // Stop I2C communication after selecting Sec Register twi_start(); // Start I2C communication twi_write(Clock_Read); // connect to DS1307(Read mode) by sending its ID rtc->sec = twi_read(1); // read second and return Positive ACK rtc->min = twi_read(1); // read minute and return Positive ACK rtc->hour= twi_read(1); // read hour and return Negative/No ACK rtc->weekDay = twi_read(1); // read weekDay and return Positive ACK rtc->date= twi_read(1); // read Date and return Positive ACK rtc->month=twi_read(1); // read Month and return Positive ACK rtc->year =twi_read(0); // read Year and return Negative/No ACK twi_stop(); // Stop I2C communication after reading the Date } twi.c : #include "twi.h" #include <avr/io.h> #include <util/delay.h> // procedura inicjalizacji TWI void twi_init(void) { /* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */ TWSR = 0; /* no prescaler */ TWBR = ((F_CPU/SCL_CLOCK)-16)/2; /* must be > 10 for stable operation */ }/* twi_init */ // procedura transmisji sygnału START void twi_start(void) { TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); } // procedura transmisji sygnału STOP void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO); while ((TWCR & (1 << TWSTO))); } // procedura transmisji bajtu danych void twi_write(char data) { TWDR = data; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); } //procedura odczytu bajtu danych char twi_read(char ack) { TWCR = ack ? ((1 << TWINT) | (1 << TWEN) | (1 << TWEA)) : ((1 << TWINT) | (1 << TWEN)); while (!(TWCR & (1 << TWINT))); return TWDR; } Gdy próbuję bezpośrednio wyświetlić zmienną time.sec kompilator ostrzega o niezgodności znaków. Gdy spróbowałem wyświetlić 'time.sec' na wyświetlacz pojawiły się dodatkowe 4 znaki z lewej strony - w sumie było ich 7, dwa ostatnie takie same jak na filmie a reszta kompletnie randomowa. Bit odczytu i zapisu wziąłem z dokumentacji urządzenia. Moje podejrzenie jest takie że zmienna którą wprowadzam do funkcji RTC_SetDateTime nie jest zmieniana przez tą funkcję. Próbowałem napisać podobną funkcję zwracającą całą strukturę, ale nie udało mi się to. Moja zmienna 'time' jest globalna i jak wrzucam ją do funkcji to moim zdaniem powinna być przez tą funkcję nadpisywana. Nie widzę sensu dodania tabeli int'ów do funkcji a potem jej wyprowadzenia bo czuje że to się skończy tak samo - mogę się mylić. Dajcie proszę jakąś wskazówkę dla mniej doświadczonego bo już nie wiem co mogę jeszcze zrobić. Doniczkav2.zip
  23. Czesc, Mam problem z programem a mianowicie nie działa mimo poprawnej składni. Konsultowałem go już z kilkoma osobami, jednak nikt nie był mi w stanie pomóc. W C działa jak jest zapisany w programie a w C++ w ogóle nie mogłem go odpalić. Pomogliście mi w 2014 to mam i nadzieję że pomożecie dzisiaj ;D #include <stdio.h> #include <stdlib.h> //Generator liczb pierwszych do 535 liczb. //Maksymalna deklaracja tablicy 124 999 999; //Dziala do zakresu 3856 - powyzej sie zaawiesza. //Wyswietlanie wszystkich liczb spowalnia //Jednakowo dziala dla CodeBlock i Dev unsigned long int tab[8048]; unsigned long int pierwsza[8048]={2,3}; //Poczštkowy zbior dwoch liczb int main() { //deklaracja zmiennych unsigned long int a,b,c,m,i,p,n; //podanie zakresu do jakiego ma szukac printf("Podaj wartosc zakresu dla wyznaczenia liczb :"); scanf("%lu", &m); //Wypelnienie tablicy liczbami z zakresu for(i=0;i<=m;i++) { tab=i; } //Wartosci poczatkowe a=1; //zakres b=2; //Ilosc liczb pierwszych c=1; while(c<=m) { //wyznaczenie zakresu na podstawie zaleznosci a=pierwsza[b-1]*2; //zerowanie w tablicy wartosci nie bedacych pierwszymi //dla danego zakresu for(i=0;i<b;i++) { n=2; while(pierwsza * n<=a) { p = pierwsza * n; tab[p]=0; n++; } } //policzenie liczb pierwszych b=0; for(i=2;i<=a;i++) { if (tab!=0) { b++; } } //Wpisanie do tablicy nowych liczb pierwszych n=0; for(i=2;i<=a;i++) { if(tab!=0) { pierwsza[n]=tab; n++; } } //c=pierwsza[b-1]; >> tak powinno być c=pierwsza[b-1]+4; //działa tylko dla 4 i więcej } printf("Liczby to:\n"); //wyswietlenie liczb for(i=0;i<b;i++) { printf("%lu\n", pierwsza); } printf("\nNajwieksza liczba pierwsza %lu\n", pierwsza[b-1]); printf("\nLiczb pierwszych jest:%lu\n", b); system("PAUSE"); return 0; }
  24. Cześć, każdy z nas musi od czasu do czasu odświeżać sobie znane już wiadomości z używanego języka programowania (szczególnie, że "na co dzień" nie używamy wszystkich z zaawansowanych konstrukcji języka) lub uczyć się nowych rzeczy dla szybko zmieniających się standardów. Ja "na co dzień" używam języka C (nieobiektowego ponieważ w programowaniu mikro-kontrolerów nadal rzadko korzysta się z C++). Pomyślałem sobie, że fajnie byłoby na forum, gdybyśmy co jakiś czas zadawali sobie pytania dotyczące programowania w Językach C/C++ dotyczące jakichś trudniejszych do zrozumienia konstrukcji języka. Oto moje pierwsze pytanie: Załóżmy, że mamy definicję tablicy tab: int tab[5][4] = { {23, 2, 7, 1}, {4, 1}, {[2]=2, 11}, {[1]=27, 0, 7}, {9, 2, 1} }; Kto wie jaka będzie wartość wyrażenia: *(*(tab+2)+1) Zakładamy, że używamy kompilatora C zgodnego przynajmniej ze standardem C99 np. popularnego gcc (Linux, można go też uzywać pod Windows instalując pakiet "mingw"). Proszę, o krótkie uzasadnienie teoretyczne odpowiedzi (ponieważ można napisać prosty program i sprawdzić wartość). Czekam też na ciekawe pytania dotyczące programowania w C/C++ od Was 😉 Pozdrawiam
  25. Dzień Dobry Forumowicze! Problem dotyczy zarówno mikrokontrolera, jak i RPi, więc mam nadzieje że nie będzie problemu związanego z nieodpowiednim działem. Od kilku dni borykam się bezskutecznie z pewnym problemem, a prezentuje się on następująco: Potrzebuję wymienić dane poprzez SPI między Raspberry Pi Zero W (Master), a Atmegą64(Slave). Atmega zasilana przez 5V na własnoręcznie zaprojektowanej płytce (z konwerterem poziomów logicznych 3,3V-5V na MOSFET'ach). Generalnie elektrycznie wszystko jest sprawne i sprawdzone kilkukrotnie, więc to odpada. Jestem w stanie zaprogramować AVR'a przez RPi za pośrednictwem SPI właśnie (na RPi Rasbian w wersji 9), z wykorzystaniem AVRDUDE. Problem jaki się pojawia, to przy próbie wymiany danych między nimi. AVR'a programuje w C, natomiast RPi w Pythonie (kody programów niżej). Polega on na tym, że biblioteka Python'a SpiDev, jako sygnał ChipSelect podaje stan wysoki, podczas gdy ATMEGA wymaga podczas tej komunikacji stanu niskiego. Atmega nie posiada możliwości zmiany trybu na taki, aby czytała stan wysoki, a biblioteka SpiDev z kolei, nie ma funkcjonalności podania stanu niskiego. Chciałem to obejść poprzez podpięcie nóżki Atmegi pod zupełnie inną nóżkę RPi i ręcznego wysterowywania tej nóżki przed nadaniem paczki danych, jednak to nie działa - nie wiem jednak dlaczego. Nie używałem nigdy wcześniej SPI, więc finalnie nie jestem nawet pewien gdzie leży problem - czy w kodzie Slav'a, Mastera czy zupełnie gdzie indziej. Slave (C, Amtega64): #define F_CPU 1000000UL #include<avr/io.h> #include<util/delay.h> #include<avr/interrupt.h> #define ustaw(bajt, nr_bitu) (bajt |=(1<<nr_bitu)) #define skasuj(bajt, nr_bitu) (bajt &=~(1<<nr_bitu)) #define sprawdz(bajt, nr_bitu) ((bajt &(1<<nr_bitu))&&1) #define sleep(czas) for (int i=0; i<(czas); i++) _delay_ms(1) int data=500; void init_spi(void) { DDRB=(1<<PB3); //MISO - output SPCR=(1<<SPE)|(1<<SPIE); //SPI_ON & Interrupt SPDR=0; } uint8_t rec_spi(void) { while (!(SPSR & (1<<SPIF))) ; return SPDR; //return data register } int main(void) { sei(); init_spi(); DDRC |=1<<PC3; //LED'y DDRC |=1<<PC4; DDRC |=1<<PC5; while (1) { ustaw(PORTC, PC4); sleep(data); skasuj(PORTC, PC4); sleep(data); } } ISR (SPI_STC_vect) { data = rec_spi(); ustaw(PORTC,PC5); } Master (Python, RPi): import RPi.GPIO as GPIO import spidev import time GPIO.setmode(GPIO.BCM) GPIO.setup(7, GPIO.OUT) GPIO.output(7, GPIO.HIGH) spi = spidev.SpiDev() spi.open(0, 0) print('Open SPI') #GPIO.output(7, GPIO.LOW) data = 0xAA try: while True: print('Sending..') GPIO.output(7, GPIO.LOW) spi.xfer([data], 50000, 100, 8) # spi.writebytes([250]) GPIO.output(7, GPIO.HIGH) print('complete') time.sleep(2) #end while except KeyboardInterrupt: # sleep(0.1) spi.close() GPIO.cleanup() Na masterze (RPi) próbowałem ustawiać różne tryby (spi.mode), różne prędkości, próbowałem z spi.writebytes oraz z spi.xfer. Wszystko bez skutku. Na Atmedze, mrugam diodą co pół sekundy. Próbowałem osiągnąć taki efekt, by wysłać liczbę 250 i ustawić ją jako czas mrugania, co zauważyłbym jako szybsze mruganie diody. Próbowałem też zapalić inną diodę w przerwaniu od SPI - wszystko bezskutecznie. SPI w FuseBitach jest aktywne. Połączenia elektryczne są poprawne, przy czym Atmegowski SS jest podłączony do 7 pinu RPi. Byłbym bardzo wdzięczny za wszelką pomoc i sugestie. Pozdrawiam.
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.